From d6f23f6ac053cc16fd4864246024ed77603edb6f Mon Sep 17 00:00:00 2001 From: Charlie Hopkins-Brinicombe Date: Wed, 15 Apr 2026 15:15:48 +0100 Subject: [PATCH 1/7] Remove duplicate file --- api-reference/openapi.yml | 6288 ------------------------------------- 1 file changed, 6288 deletions(-) delete mode 100644 api-reference/openapi.yml diff --git a/api-reference/openapi.yml b/api-reference/openapi.yml deleted file mode 100644 index d29c003..0000000 --- a/api-reference/openapi.yml +++ /dev/null @@ -1,6288 +0,0 @@ -openapi: 3.1.0 -info: - title: Trophy - version: '1.2.1' -paths: - # APPLICATION API ------------------------------------------------------- - - /achievements: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get all achievements and their completion stats. - operationId: achievements_all - x-fern-server-name: api - tags: - - Achievements - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.achievements.all(); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.achievements.all() - parameters: - - name: userAttributes - in: query - description: Optional colon-delimited user attributes in the format attribute:value,attribute:value. Only achievements accessible to a user with the provided attributes will be returned. - required: false - schema: - type: string - example: plan-type:premium,region:us-east - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/AchievementWithStatsResponse' - examples: - Successful operation: - value: - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - trigger: api - name: Finish onboarding - description: Complete the onboarding process. - badgeUrl: https://example.com/badge.png - key: finish-onboarding - completions: 8 - rarity: 80 - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123683 - trigger: metric - name: 500 words written - description: Write 500 words in the app. - badgeUrl: https://example.com/badge.png - metricId: 5100fe51-6bce-6j44-b0hs-bddc4e123683 - metricName: words written - metricValue: 500 - completions: 6 - rarity: 60 - userAttributes: - - key: plan-type - value: premium - - key: region - value: us-east - eventAttribute: - key: source - value: mobile-app - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123684 - trigger: streak - name: 10 days of exercise - description: Exercise at least once a day for 10 days in a row. - badgeUrl: https://example.com/badge.png - streakLength: 10 - completions: 2 - rarity: 20 - userAttributes: - - key: plan-type - value: premium - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get all achievements and their completion stats - security: - - ApiKeyAuth: [] - /achievements/{key}/complete: - servers: - - url: https://api.trophy.so/v1 - description: Application API - post: - description: Mark an achievement as completed for a user. - operationId: achievements_complete - x-fern-server-name: api - tags: - - Achievements - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.achievements.complete("achievement-key", { - user: { - id: "user-id", - } - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - user = UpsertedUser(id="123") - - response = client.achievements.complete("achievement-key", user=user) - parameters: - - name: key - in: path - description: Unique reference of the achievement as set when created. - required: true - schema: - type: string - example: finish-onboarding - responses: - '201': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/AchievementCompletionResponse' - examples: - Successful operation: - value: - completionId: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - achievement: - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - trigger: api - name: Finish onboarding - description: Complete the onboarding process. - badgeUrl: https://example.com/badge.png - key: finish-onboarding - achievedAt: '2021-01-01T00:00:00Z' - points: - points-system-key: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - name: XP - description: null - badgeUrl: null - total: 10 - added: 10 - awards: - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - awarded: 10 - date: '2021-01-01T00:00:00Z' - total: 10 - trigger: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - type: achievement - achievementName: Finish onboarding - points: 10 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Achievement Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Mark an achievement as completed - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - user: - $ref: '#/components/schemas/UpsertedUser' - description: The user that completed the achievement. - required: - - user - examples: - Successful operation: - value: - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - /metrics/{key}/event: - servers: - - url: https://api.trophy.so/v1 - description: Application API - post: - description: Increment or decrement the value of a metric for a user. - operationId: metrics_event - x-fern-server-name: api - tags: - - Metrics - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.metrics.event( - "words-written", - { - user: { - id: 'user-id', - email: 'user@example.com', - tz: 'Europe/London', - subscribedToEmails: true, - attributes: { - department: 'engineering', - role: 'developer' - } - }, - value: 750, - attributes: { - category: 'writing', - source: 'mobile-app' - } - } - ); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - user = UpsertedUser( - id="123", - email="user@example.com", - tz="Europe/London", - subscribedToEmails=True, - attributes={ - "department": "engineering", - "role": "developer" - } - ) - - response = client.metrics.event( - "words-written", - user=user, - value=750, - attributes={ - "category": "writing", - "source": "mobile-app" - }) - parameters: - - name: key - in: path - description: Unique reference of the metric as set when created. - required: true - schema: - type: string - example: words-written - - name: Idempotency-Key - in: header - description: The idempotency key for the event. - required: false - schema: - type: string - example: e4296e4b-8493-4bd1-9c30-5a1a9ac4d78f - responses: - '201': - description: Created event - content: - application/json: - schema: - $ref: '#/components/schemas/EventResponse' - examples: - Successful operation: - value: - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - eventId: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - total: 750 - achievements: - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - trigger: metric - metricId: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - metricName: words written - metricValue: 500 - name: 500 words written - description: Write 500 words in the app. - achievedAt: '2020-01-01T00:00:00Z' - currentStreak: - length: 1 - frequency: daily - started: '2025-04-02' - periodStart: '2025-03-31' - periodEnd: '2025-04-05' - expires: '2025-04-12' - points: - xp: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - name: XP - description: null - badgeUrl: null - total: 10 - level: - id: 1140fe51-6bce-4b44-b0ad-bddc4e123534 - key: bronze - name: Bronze - description: Starting level - badgeUrl: null - points: 0 - added: 10 - awards: - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - awarded: 10 - date: '2021-01-01T00:00:00Z' - total: 10 - trigger: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - type: metric - metricName: words written - metricThreshold: 100 - points: 10 - leaderboards: - all-time: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123535 - key: all-time - name: All-Time Leaderboard - description: null - rankBy: metric - runUnit: null - runInterval: null - maxParticipants: 100 - breakdownAttribute: null - metricName: words written - metricKey: words-written - threshold: 10 - start: '2025-01-01' - end: null - previousRank: null - rank: 100 - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Send a metric change event - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - user: - $ref: '#/components/schemas/UpsertedUser' - description: The user that triggered the event. - value: - type: number - format: double - description: >- - The value to add to the user's current total for the given - metric. - example: 750 - attributes: - type: object - additionalProperties: - type: string - description: Event attributes as key-value pairs. Keys must match existing event attributes set up in the Trophy dashboard. - example: - category: writing - source: mobile-app - required: - - user - - value - examples: - Successful operation: - value: - user: - email: user@example.com - tz: Europe/London - id: '18' - attributes: - department: engineering - role: developer - value: 750 - attributes: - category: writing - source: mobile-app - /users: - servers: - - url: https://api.trophy.so/v1 - description: Application API - post: - description: Create a new user. - operationId: users_create - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.create({ - id: 'user-id', - email: 'user@example.com', - tz: 'Europe/London', - subscribedToEmails: true, - attributes: { - department: 'engineering', - role: 'developer' - } - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.create( - id="123", - email="user@example.com", - tz="Europe/London", - subscribedToEmails=True, - attributes={ - "department": "engineering", - "role": "developer" - } - ) - summary: Create a new user - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - $ref: '#/components/schemas/UpsertedUser' - description: The user object. - responses: - '201': - description: Identified user - content: - application/json: - schema: - $ref: '#/components/schemas/User' - examples: - Successful operation: - value: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - /users/{id}: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a single user. - operationId: users_get - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.get("user-id"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.get("user-id") - summary: Get a single user - parameters: - - name: id - in: path - description: ID of the user to get. - required: true - schema: - type: string - example: userId - security: - - ApiKeyAuth: [] - responses: - '200': - description: Found user - content: - application/json: - schema: - $ref: '#/components/schemas/User' - examples: - Successful operation: - value: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - put: - description: Identify a user. - operationId: users_identify - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.identify("user-id", { - email: 'user@example.com', - tz: 'Europe/London', - attributes: { - department: 'engineering', - role: 'developer' - } - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.identify( - id="123", - email="user@example.com", - tz="Europe/London", - subscribedToEmails=True, - attributes={ - "department": "engineering", - "role": "developer" - } - ) - summary: Identify a user - security: - - ApiKeyAuth: [] - parameters: - - name: id - in: path - description: ID of the user to identify. - required: true - schema: - type: string - example: id - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdatedUser' - description: The user object. - example: - email: user@example.com - tz: Europe/London - attributes: - department: engineering - role: developer - responses: - '200': - description: Upserted user - content: - application/json: - schema: - $ref: '#/components/schemas/User' - examples: - Successful operation: - value: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - patch: - description: Update a user. - operationId: users_update - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.update("user-id", { - email: 'user@example.com', - tz: 'Europe/London', - attributes: { - department: 'engineering', - role: 'developer' - } - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.update( - id="123", - email="user@example.com", - tz="Europe/London", - subscribedToEmails=True, - attributes={ - "department": "engineering", - "role": "developer" - } - ) - summary: Update a user - security: - - ApiKeyAuth: [] - parameters: - - name: id - in: path - description: ID of the user to update. - required: true - schema: - type: string - example: id - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdatedUser' - description: The user object. - example: - id: user-id - email: user@example.com - tz: Europe/London - attributes: - department: engineering - role: developer - responses: - '200': - description: Updated user - content: - application/json: - schema: - $ref: '#/components/schemas/User' - examples: - Successful operation: - value: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'User Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - /users/{id}/preferences: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a user's notification preferences. - operationId: users_get_preferences - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.getPreferences("user-123"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.get_preferences(id="user-123") - parameters: - - name: id - in: path - description: The user's ID in your database. - required: true - schema: - type: string - example: user-123 - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/UserPreferencesResponse' - examples: - Successful operation: - value: - notifications: - achievement_completed: - - email - - push - recap: - - email - reactivation: - - push - streak_reminder: - - email - - push - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'User not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a user's preferences - security: - - ApiKeyAuth: [] - patch: - description: Update a user's notification preferences. - operationId: users_update_preferences - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.updatePreferences("user-123", { - notifications: { - recap: ["email"], - streak_reminder: [] - } - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.update_preferences( - id="user-123", - notifications={ - "recap": ["email"], - "streak_reminder": [] - } - ) - parameters: - - name: id - in: path - description: The user's ID in your database. - required: true - schema: - type: string - example: user-123 - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/UserPreferencesResponse' - examples: - Successful operation: - value: - notifications: - achievement_completed: - - email - - push - recap: - - email - reactivation: - - email - - push - streak_reminder: [] - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'User not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Update a user's preferences - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateUserPreferencesRequest' - examples: - Disable streak reminders: - value: - notifications: - streak_reminder: [] - Email only for recaps: - value: - notifications: - recap: - - email - Enable all for achievements: - value: - notifications: - achievement_completed: - - email - - push - /users/{id}/metrics: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a single user's progress against all active metrics. - operationId: users_all_metrics - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.allMetrics("user-id"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.all_metrics("user-id") - parameters: - - name: id - in: path - description: ID of the user - required: true - schema: - type: string - example: userId - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/MetricResponse' - examples: - Successful operation: - value: - - id: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - key: words-written - name: Words written - status: active - current: 4500 - achievements: - - id: abe3120f-5ca9-4344-92c8-5b891643a04b - trigger: metric - name: Novice Writer - description: 'null' - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - metricValue: 500 - achievedAt: '2021-01-01T00:00:00Z' - badgeUrl: https://example.com/badge1.png - - id: 8a07f2d0-9c72-4de1-bf92-9530ae82b4b6 - trigger: metric - name: Intermediate Writer - description: 'null' - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - metricValue: 1000 - achievedAt: '2021-01-02T00:00:00Z' - badgeUrl: https://example.com/badge2.png - - id: 2090d038-aa04-4048-ab2e-e2b7bf2d3b9f - trigger: metric - name: Expert Writer - description: 'null' - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - metricValue: 2000 - achievedAt: null - badgeUrl: 'null' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'User Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get all metrics for a user - security: - - ApiKeyAuth: [] - /users/{id}/metrics/{key}: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a user's progress against a single active metric. - operationId: users_single_metric - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.singleMetric("user-id", "words-written"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.single_metric(id="user-id", key="words-written") - parameters: - - name: id - in: path - description: ID of the user. - required: true - schema: - type: string - example: userId - - name: key - in: path - description: Unique key of the metric. - required: true - schema: - type: string - example: key - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/MetricResponse' - examples: - Successful operation: - value: - id: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - key: words-written - name: Words written - status: active - current: 1500 - achievements: - - id: abe3120f-5ca9-4344-92c8-5b891643a04b - trigger: metric - name: Novice Writer - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - metricValue: 500 - achievedAt: '2021-01-01T00:00:00Z' - - id: 8a07f2d0-9c72-4de1-bf92-9530ae82b4b6 - trigger: metric - name: Intermediate Writer - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - metricValue: 1000 - achievedAt: '2021-01-02T00:00:00Z' - - id: 2090d038-aa04-4048-ab2e-e2b7bf2d3b9f - trigger: metric - name: Expert Writer - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - metricValue: 2000 - achievedAt: null - badgeUrl: https://example.com/badge.png - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a single metric for a user - security: - - ApiKeyAuth: [] - /users/{id}/metrics/{key}/event-summary: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a summary of metric events over time for a user. - operationId: users_metric_event_summary - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.metricEventSummary("user-id", "words-written", { - aggregation: "daily", - startDate: "2024-01-01", - endDate: "2024-01-31" - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.metric_event_summary( - id="user-id", - key="words-written", - aggregation="daily", - start_date="2024-01-01", - end_date="2024-01-31" - ) - parameters: - - name: id - in: path - description: ID of the user. - required: true - schema: - type: string - example: userId - - name: key - in: path - description: Unique key of the metric. - required: true - schema: - type: string - example: words-written - - name: aggregation - in: query - description: The time period over which to aggregate the event data. - required: true - schema: - type: string - enum: - - daily - - weekly - - monthly - example: daily - - name: startDate - in: query - description: The start date for the data range in YYYY-MM-DD format. The startDate must be before the endDate, and the date range must not exceed 400 days. - required: true - schema: - type: string - format: date - example: '2024-01-01' - - name: endDate - in: query - description: The end date for the data range in YYYY-MM-DD format. The endDate must be after the startDate, and the date range must not exceed 400 days. - required: true - schema: - type: string - format: date - example: '2024-01-31' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - type: object - properties: - date: - type: string - format: date - description: The date of the data point. For weekly or monthly aggregations, this is the first date of the period. - example: '2024-01-01' - total: - type: number - format: double - description: The user's total for this metric at the end of this date. - example: 100 - change: - type: number - format: double - description: The change in the user's total for this metric during this period. - example: 50 - required: - - date - - total - - change - examples: - Successful operation: - value: - - date: '2024-01-01' - total: 100 - change: 100 - - date: '2024-01-02' - total: 300 - change: 200 - - date: '2024-01-03' - total: 600 - change: 300 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a summary of metric events over time - security: - - ApiKeyAuth: [] - /users/{id}/achievements: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a user's achievements. - operationId: users_achievements - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - label: Get a user's completed achievements - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.achievements("user-id"); - - lang: javascript - label: Get a user's achievements (include incomplete) - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.achievements("user-id", { - includeIncomplete: true - }); - - lang: python - label: Get a user's completed achievements - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.achievements(id="user-id") - - lang: python - label: Get a user's achievements (include incomplete) - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.achievements(id="user-id", include_incomplete="true") - parameters: - - name: id - in: path - description: ID of the user. - required: true - schema: - type: string - example: userId - - name: includeIncomplete - in: query - description: When set to 'true', returns both completed and incomplete achievements for the user. When omitted or set to any other value, returns only completed achievements. - required: false - schema: - type: string - enum: ['true'] - example: 'true' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/UserAchievementWithStatsResponse' - examples: - Successful operation: - value: - - id: d01dcbcb-d51e-4c12-b054-dc811dcdc625 - name: Completed Onboarding - trigger: api - key: completed-onboarding - achievedAt: '2021-01-01T00:00:00Z' - badgeUrl: https://example.com/badge2.png - completions: 100 - rarity: 50 - - id: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - trigger: metric - key: novice-writer - metricId: d01dcbcb-d51e-4c12-b054-dc811dcdc619 - metricValue: 500 - metricName: words written - name: Novice Writer - achievedAt: '2021-02-01T00:00:00Z' - badgeUrl: https://example.com/badge1.png - completions: 100 - rarity: 50 - - id: d01dcbcb-d51e-4c12-b054-dc811dcdc624 - trigger: streak - key: 3-day-streak - streakLength: 3 - name: 3-Day Streak - achievedAt: '2021-03-01T00:00:00Z' - badgeUrl: https://example.com/badge2.png - completions: 100 - rarity: 50 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a user's achievements - security: - - ApiKeyAuth: [] - /users/{id}/streak: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a user's streak data. - operationId: users_streak - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.streak("user-id", { - historyPeriods: 14 - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.streak(id="user-id", history_periods=14) - parameters: - - name: id - in: path - description: ID of the user. - required: true - schema: - type: string - example: userId - - in: query - name: historyPeriods - schema: - type: integer - default: 7 - description: >- - The number of past streak periods to include in the streakHistory field of the - response. - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: object - $ref: '#/components/schemas/StreakResponse' - examples: - Successful operation: - value: - length: 1 - frequency: weekly - started: '2025-04-02' - periodStart: '2025-03-31' - periodEnd: '2025-04-05' - expires: '2025-04-12' - rank: 5 - streakHistory: - - periodStart: '2025-03-30' - periodEnd: '2025-04-05' - length: 1 - - periodStart: '2025-04-06' - periodEnd: '2025-04-12' - length: 2 - - periodStart: '2025-04-13' - periodEnd: '2025-04-19' - length: 3 - - periodStart: '2025-04-20' - periodEnd: '2025-04-26' - length: 0 - - periodStart: '2025-04-27' - periodEnd: '2025-05-03' - length: 1 - - periodStart: '2025-05-04' - periodEnd: '2025-05-10' - length: 2 - - periodStart: '2025-05-11' - periodEnd: '2025-05-17' - length: 3 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a user's streak status - security: - - ApiKeyAuth: [] - /streaks: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get the streak lengths of a list of users, ranked by streak length from longest to shortest. - operationId: streaks_list - x-fern-server-name: api - tags: - - Streaks - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const response = await trophy.streaks.list({ - userIds: ['user-123', 'user-456', 'user-789'] - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.streaks.list(user_ids=["user-123", "user-456", "user-789"]) - parameters: - - name: userIds - in: query - description: A list of up to 100 user IDs. - required: true - schema: - type: array - items: - type: string - example: 'user-123,user-456,user-789' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/BulkStreakResponse' - examples: - Successful operation: - value: - - userId: user-123 - streakLength: 15 - extended: '2025-01-01T05:03:00Z' - - userId: user-456 - streakLength: 12 - extended: '2025-01-01T08:43:00Z' - - userId: user-789 - streakLength: 0 - extended: null - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get the streak lengths of a list of users - security: - - ApiKeyAuth: [] - /streaks/rankings: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get the top users by streak length (active or longest). - operationId: streaks_rankings - x-fern-server-name: api - tags: - - Streaks - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.streaks.rankings({ - limit: 20, - type: 'active' - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.streaks.rankings(limit=20, type="active") - parameters: - - name: limit - in: query - description: Number of users to return. Must be between 1 and 100. - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 10 - example: 20 - - name: type - in: query - description: Whether to rank users by active streaks or longest streaks ever achieved. - required: false - schema: - type: string - enum: ['active', 'longest'] - default: 'active' - example: 'active' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/StreakRankingUser' - examples: - Successful operation: - value: - - userId: user-123 - name: Alice Johnson - streakLength: 15 - - userId: user-456 - name: Bob Smith - streakLength: 12 - - userId: user-789 - name: Charlie Brown - streakLength: 8 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get top users by streak length - security: - - ApiKeyAuth: [] - /users/{id}/points/{key}: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a user's points for a specific points system. - operationId: users_points - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.points("user-id", "points-system-key"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.points(id="user-id", key="points-system-key") - parameters: - - name: id - in: path - description: ID of the user. - required: true - schema: - type: string - example: userId - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - - in: query - name: awards - schema: - type: integer - default: 10 - minimum: 1 - maximum: 100 - description: The number of recent point awards to return. - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/GetUserPointsResponse' - examples: - Successful operation: - value: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - key: xp - name: XP - description: null - badgeUrl: null - maxPoints: null - total: 100 - level: - id: 1140fe51-6bce-4b44-b0ad-bddc4e123534 - key: silver - name: Silver - description: Mid-tier level - badgeUrl: null - points: 50 - awards: - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - awarded: 10 - date: '2021-01-01T00:00:00Z' - total: 100 - trigger: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - type: metric - points: 10 - metricName: words written - metricThreshold: 1000 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a user's points data - security: - - ApiKeyAuth: [] - /users/{id}/points/{key}/boosts: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get active points boosts for a user in a specific points system. Returns both global boosts the user is eligible for and user-specific boosts. - operationId: users_points_boosts - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.pointsBoosts("user-id", "points-system-key"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.points_boosts(id="user-id", key="points-system-key") - parameters: - - name: id - in: path - description: ID of the user. - required: true - schema: - type: string - example: userId - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PointsBoost' - examples: - Successful operation: - value: - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - name: Double XP Weekend - status: active - start: '2025-01-01' - end: '2025-01-03' - multiplier: 2 - rounding: 'down' - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123535 - name: VIP Bonus - status: active - start: '2025-01-01' - end: null - multiplier: 1.5 - rounding: 'nearest' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a user's active points boosts - security: - - ApiKeyAuth: [] - /users/{id}/points/{key}/event-summary: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a summary of points awards over time for a user for a specific points system. - operationId: users_points_event_summary - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.pointsEventSummary("user-id", "points-system-key", { - aggregation: "daily", - startDate: "2024-01-01", - endDate: "2024-01-31" - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.points_event_summary( - id="user-id", - key="points-system-key", - aggregation="daily", - start_date="2024-01-01", - end_date="2024-01-31" - ) - parameters: - - name: id - in: path - description: ID of the user. - required: true - schema: - type: string - example: userId - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - - name: aggregation - in: query - description: The time period over which to aggregate the event data. - required: true - schema: - type: string - enum: - - daily - - weekly - - monthly - example: daily - - name: startDate - in: query - description: The start date for the data range in YYYY-MM-DD format. The startDate must be before the endDate, and the date range must not exceed 400 days. - required: true - schema: - type: string - format: date - example: '2024-01-01' - - name: endDate - in: query - description: The end date for the data range in YYYY-MM-DD format. The endDate must be after the startDate, and the date range must not exceed 400 days. - required: true - schema: - type: string - format: date - example: '2024-01-31' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - type: object - properties: - date: - type: string - format: date - description: The date of the data point. For weekly or monthly aggregations, this is the first date of the period. - example: '2024-01-01' - total: - type: number - format: double - description: The user's total points at the end of this date. - example: 100 - change: - type: number - format: double - description: The change in the user's total points during this period. - example: 50 - required: - - date - - total - - change - examples: - Successful operation: - value: - - date: '2024-01-01' - total: 100 - change: 100 - - date: '2024-01-02' - total: 300 - change: 200 - - date: '2024-01-03' - total: 600 - change: 300 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a summary of points events over time - security: - - ApiKeyAuth: [] - /users/{id}/leaderboards/{key}: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a user's rank, value, and history for a specific leaderboard. - operationId: users_leaderboard - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.leaderboards("user-123", "weekly-words", { - run: "2025-01-15" - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.leaderboards( - user_id="user-123", - key="weekly-words", - run="2025-01-15" - ) - parameters: - - name: id - in: path - description: The user's ID in your database. - required: true - schema: - type: string - example: user-123 - - name: key - in: path - description: Unique key of the leaderboard as set when created. - required: true - schema: - type: string - example: weekly-words - - name: run - in: query - description: Specific run date in YYYY-MM-DD format. If not provided, returns the current run. - required: false - schema: - type: string - format: date - example: '2025-01-15' - - name: numEvents - in: query - description: The number of events to return in the history array. - required: false - schema: - type: integer - default: 10 - minimum: 1 - maximum: 100 - example: 10 - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/UserLeaderboardResponseWithHistory' - examples: - Successful operation: - value: - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: Weekly Word Count Challenge - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - description: Compete weekly to see who writes the most words - start: '2025-01-01' - end: null - maxParticipants: 100 - breakdownAttribute: null - runUnit: day - runInterval: 7 - rank: 2 - value: 4500 - history: - - timestamp: '2025-01-15T10:30:00Z' - previousRank: null - rank: 5 - previousValue: null - value: 1000 - - timestamp: '2025-01-15T14:15:00Z' - previousRank: 5 - rank: 3 - previousValue: 1000 - value: 3000 - - timestamp: '2025-01-15T18:45:00Z' - previousRank: 3 - rank: 2 - previousValue: 3000 - value: 4500 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'User or leaderboard not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a user's leaderboard data - security: - - ApiKeyAuth: [] - /users/{id}/wrapped: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a user's year-in-review wrapped data. - operationId: users_wrapped - x-fern-server-name: api - tags: - - Users - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.users.wrapped("user-123", { - year: 2024 - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.users.wrapped(id="user-123", year=2024) - parameters: - - name: id - in: path - description: The user's ID in your database. - required: true - schema: - type: string - example: user-123 - - name: year - in: query - description: The year to get wrapped data for. Defaults to the current year. Must be an integer between 1 and the current year. - required: false - schema: - type: integer - minimum: 1 - example: 2024 - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/WrappedResponse' - examples: - Successful operation: - value: - user: - id: user-123 - email: user@example.com - name: John Doe - tz: America/New_York - subscribeToEmails: true - created: '2024-01-15T10:30:00Z' - updated: '2024-06-20T14:45:00Z' - control: false - attributes: - plan-type: premium - region: us-east - activity: - daysActive: 156 - weeksActive: 42 - monthsActive: 11 - mostActiveDay: - date: '2024-03-15' - metrics: - words-written: - name: Words Written - units: words - currentTotal: 15000 - changeThisPeriod: 2500 - percentChange: 20 - byAttribute: {} - points: - xp-system: - name: Experience Points - description: Points earned through activity - currentTotal: 5000 - changeThisPeriod: 500 - percentChange: 11.1 - achievements: - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: 500 Words Written - trigger: metric - description: Write 500 words in a single day - badgeUrl: https://example.com/badge.png - key: 500-words - metricId: metric-123 - metricValue: 500 - metricName: Words Written - achievedAt: '2024-03-15T14:30:00Z' - completions: 150 - rarity: 15 - leaderboards: - weekly-words: - id: leaderboard-123 - name: Weekly Word Count - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - description: Weekly writing competition - start: '2024-03-11' - end: '2024-03-17' - maxParticipants: 100 - runUnit: day - runInterval: 7 - rank: 3 - value: 2500 - mostActiveWeek: - start: '2024-03-11' - end: '2024-03-17' - metrics: - words-written: - name: Words Written - units: words - currentTotal: 15000 - changeThisPeriod: 8500 - percentChange: 130 - percentileThisPeriod: 95 - byAttribute: {} - points: - xp-system: - name: Experience Points - description: Points earned through activity - currentTotal: 5000 - changeThisPeriod: 1200 - percentChange: 31.5 - percentileThisPeriod: 88 - achievements: [] - leaderboards: {} - mostActiveMonth: - month: 2 - metrics: - words-written: - name: Words Written - units: words - currentTotal: 15000 - changeThisPeriod: 12000 - percentChange: 400 - percentileThisPeriod: 92 - byAttribute: {} - points: - xp-system: - name: Experience Points - description: Points earned through activity - currentTotal: 5000 - changeThisPeriod: 2000 - percentChange: 66.6 - percentileThisPeriod: 85 - achievements: [] - leaderboards: {} - entireYear: - metrics: - words-written: - name: Words Written - units: words - currentTotal: 150000 - changeThisPeriod: 150000 - percentChange: 100 - percentileThisPeriod: 78 - byAttribute: {} - points: - xp-system: - name: Experience Points - description: Points earned through activity - currentTotal: 25000 - changeThisPeriod: 25000 - percentChange: 100 - percentileThisPeriod: 82 - achievements: - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: 500 Words Written - trigger: metric - description: Write 500 words in a single day - badgeUrl: https://example.com/badge.png - key: 500-words - metricId: metric-123 - metricValue: 500 - metricName: Words Written - achievedAt: '2024-03-15T14:30:00Z' - completions: 150 - rarity: 15 - leaderboards: {} - longestStreak: - length: 45 - frequency: daily - periodStart: '2024-02-01' - periodEnd: '2024-03-17' - started: '2024-02-01' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'User not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a user's wrapped data - security: - - ApiKeyAuth: [] - /points/{key}/summary: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a breakdown of the number of users with points in each range. - operationId: points_summary - x-fern-server-name: api - tags: - - Points - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.points.summary("points-system-key", { - userAttributes: "plan-type:premium,region:us-east" - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.points.summary( - key="points-system-key", - user_attributes="plan-type:premium,region:us-east" - ) - parameters: - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - - name: userAttributes - in: query - description: Optional colon-delimited user attribute filters in the format attribute:value,attribute:value. Only users matching ALL specified attributes will be included in the points breakdown. - required: false - schema: - type: string - example: plan-type:premium,region:us-east - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/PointsSummaryResponse' - examples: - Successful operation: - value: - - from: 0 - to: 0 - users: 5012 - - from: 1 - to: 100 - users: 1501 - - from: 101 - to: 200 - users: 1007 - - from: 201 - to: 300 - users: 584 - - from: 301 - to: 400 - users: 201 - - from: 401 - to: 500 - users: 102 - - from: 501 - to: 600 - users: 25 - - from: 601 - to: 700 - users: 0 - - from: 701 - to: 800 - users: 0 - - from: 801 - to: 900 - users: 0 - - from: 901 - to: 1000 - users: 0 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a breakdown of users by points - security: - - ApiKeyAuth: [] - /points/{key}: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a points system with its triggers. - operationId: points_system - x-fern-server-name: api - tags: - - Points - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.points.system("points-system-key"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.points.system(key="points-system-key") - parameters: - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/PointsSystemResponse' - examples: - Successful operation: - value: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - name: XP System - description: Experience points for user engagement - badgeUrl: https://example.com/badge.png - maxPoints: null - triggers: - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - type: metric - points: 10 - status: active - metricId: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - metricName: words written - metricThreshold: 1000 - userAttributes: - - key: plan-type - value: premium - - key: region - value: us-east - eventAttribute: - key: source - value: mobile-app - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123536 - type: streak - points: 10 - status: active - streakLengthThreshold: 7 - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123535 - type: achievement - points: 50 - status: active - achievementId: 0040fe51-6bce-4b44-b0ad-bddc4e123535 - achievementName: finish onboarding - userAttributes: - - key: plan-type - value: premium - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Points system not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a points system with its triggers - security: - - ApiKeyAuth: [] - /points/{key}/boosts: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get all global boosts for a points system. Finished boosts are excluded by default. - operationId: points_boosts - x-fern-server-name: api - tags: - - Points - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.points.boosts("points-system-key"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.points.boosts(key="points-system-key") - parameters: - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - - name: includeFinished - in: query - description: When set to 'true', boosts that have finished (past their end date) will be included in the response. By default, finished boosts are excluded. - required: false - schema: - type: boolean - default: false - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PointsBoost' - examples: - Successful operation: - value: - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123537 - name: Double XP Weekend - status: active - start: '2025-01-01' - end: '2025-01-03' - multiplier: 2 - rounding: 'down' - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123538 - name: Holiday Bonus - status: finished - start: '2024-12-25' - end: '2024-12-31' - multiplier: 1.5 - rounding: 'nearest' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Points system not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get boosts for a points system - security: - - ApiKeyAuth: [] - /points/{key}/levels: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get all levels for a points system. - operationId: points_levels - x-fern-server-name: api - tags: - - Points - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.points.levels("points-system-key"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.points.levels(key="points-system-key") - parameters: - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PointsLevel' - examples: - Successful operation: - value: - - id: 1140fe51-6bce-4b44-b0ad-bddc4e123534 - key: bronze - name: Bronze - description: Starting level - badgeUrl: https://example.com/bronze.png - points: 0 - - id: 2240fe51-6bce-4b44-b0ad-bddc4e123534 - key: silver - name: Silver - description: Mid-tier level - badgeUrl: null - points: 50 - - id: 3340fe51-6bce-4b44-b0ad-bddc4e123534 - key: gold - name: Gold - description: Top level - badgeUrl: https://example.com/gold.png - points: 200 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Points system not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get levels for a points system - security: - - ApiKeyAuth: [] - /points/{key}/level-summary: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a breakdown of the number of users at each level in a points system. - operationId: points_level_summary - x-fern-server-name: api - tags: - - Points - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.points.levelSummary("points-system-key"); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.points.level_summary(key="points-system-key") - parameters: - - name: key - in: path - description: Key of the points system. - required: true - schema: - type: string - example: points-system-key - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/PointsLevelSummaryResponse' - examples: - Successful operation: - value: - - level: - id: 1140fe51-6bce-4b44-b0ad-bddc4e123534 - key: bronze - name: Bronze - description: Starting level - badgeUrl: https://example.com/bronze.png - points: 0 - users: 5012 - - level: - id: 2240fe51-6bce-4b44-b0ad-bddc4e123534 - key: silver - name: Silver - description: Mid-tier level - badgeUrl: null - points: 50 - users: 1501 - - level: - id: 3340fe51-6bce-4b44-b0ad-bddc4e123534 - key: gold - name: Gold - description: Top level - badgeUrl: https://example.com/gold.png - points: 200 - users: 102 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Points system not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'No levels configured on the points system' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get level summary for a points system - security: - - ApiKeyAuth: [] - /leaderboards: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get all leaderboards for your organization. Finished leaderboards are excluded by default. - operationId: leaderboards_all - x-fern-server-name: api - tags: - - Leaderboards - parameters: - - name: includeFinished - in: query - description: When set to 'true', leaderboards with status 'finished' will be included in the response. By default, finished leaderboards are excluded. - required: false - schema: - type: boolean - default: false - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.leaderboards.all(); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.leaderboards.all() - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - allOf: - - $ref: '#/components/schemas/LeaderboardResponse' - - type: object - properties: - status: - type: string - enum: ['active', 'scheduled', 'finished'] - description: The status of the leaderboard. - example: active - required: - - status - examples: - Successful operation: - value: - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: Weekly Word Count Challenge - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - description: Compete weekly to see who writes the most words - status: active - start: '2025-01-01' - end: null - maxParticipants: 100 - breakdownAttribute: null - runUnit: day - runInterval: 7 - - id: 5100fe51-6bce-6j44-b0hs-bddc4e123683 - name: XP Leaderboard - key: xp-board - rankBy: points - pointsSystemKey: xp-system - pointsSystemName: Experience Points - description: Overall ranking by XP earned - status: active - start: '2025-01-01' - end: null - maxParticipants: 50 - breakdownAttribute: null - runUnit: null - runInterval: null - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get all leaderboards - security: - - ApiKeyAuth: [] - /leaderboards/{key}: - servers: - - url: https://api.trophy.so/v1 - description: Application API - get: - description: Get a specific leaderboard by its key. - operationId: leaderboards_get - x-fern-server-name: api - tags: - - Leaderboards - x-codeSamples: - - lang: javascript - label: Get rankings - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.leaderboards.get("weekly-words", { - offset: 0, - limit: 10, - run: "2025-01-15" - }); - - lang: javascript - label: Get rankings by user attribute - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.leaderboards.get("weekly-words", { - offset: 0, - limit: 10, - run: "2025-01-15", - userAttributes: "city:london" - }); - - lang: python - label: Get rankings - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.leaderboards.get( - key="weekly-words", - offset=0, - limit=10, - run="2025-01-15" - ) - - lang: python - label: Get rankings by user attribute - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.leaderboards.get( - key="weekly-words", - offset=0, - limit=10, - run="2025-01-15", - user_attributes="city:london" - ) - parameters: - - name: key - in: path - description: Unique key of the leaderboard as set when created. - required: true - schema: - type: string - example: weekly-words - - name: offset - in: query - description: Number of rankings to skip for pagination. - required: false - schema: - type: integer - minimum: 0 - default: 0 - example: 20 - - name: limit - in: query - description: Maximum number of rankings to return. Cannot be greater than the size of the leaderboard. - required: false - schema: - type: integer - minimum: 0 - default: 10 - example: 50 - - name: run - in: query - description: Specific run date in YYYY-MM-DD format. If not provided, returns the current run. - required: false - schema: - type: string - format: date - example: '2025-01-15' - - name: userId - in: query - description: When provided, offset is relative to this user's position on the leaderboard. If the user is not found in the leaderboard, returns empty rankings array. - required: false - schema: - type: string - example: 'user-123' - - name: userAttributes - in: query - description: Attribute key and value to filter the rankings by, separated by a colon. For example, `city:London`. This parameter is required, and only valid for leaderboards with a breakdown attribute. - required: false - schema: - type: string - example: city:London - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/LeaderboardResponseWithRankings' - examples: - Successful operation: - value: - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: Weekly Word Count Challenge - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - pointsSystemKey: null - pointsSystemName: null - description: Compete weekly to see who writes the most words - status: active - start: '2025-01-01' - end: null - maxParticipants: 100 - breakdownAttribute: null - runUnit: day - runInterval: 7 - rankings: - - userId: user-123 - userName: Alice Johnson - rank: 1 - value: 5000 - - userId: user-456 - userName: Bob Smith - rank: 2 - value: 4500 - - userId: user-789 - userName: Charlie Brown - rank: 3 - value: 4200 - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Leaderboard not found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Get a single leaderboard - security: - - ApiKeyAuth: [] - - # ADMIN API ------------------------------------------------------------ - - /streaks/freezes: - servers: - - url: https://admin.trophy.so/v1 - description: Admin API - post: - description: Create streak freezes for multiple users. - operationId: admin_streaks_freezes_create - x-fern-server-name: admin - x-fern-sdk-group-name: - - admin - - streaks - - freezes - x-fern-sdk-method-name: create - tags: - - Admin - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.admin.streaks.freezes.create({ - freezes: [ - { userId: 'user-123' }, - { userId: 'user-456' }, - ] - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.admin.streaks.freezes.create({ - "freezes": [ - {"userId": "user-123"}, - {"userId": "user-456"} - ] - }) - requestBody: - description: Array of freezes to create - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateStreakFreezesRequest' - examples: - Create freezes for multiple users: - value: - freezes: - - userId: user-123 - - userId: user-456 - - userId: user-123 - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CreateStreakFreezesResponse' - examples: - Success with no issues: - value: - issues: [] - Success with warnings: - value: - issues: - - userId: user-789 - level: warning - reason: Would exceed maximum freeze limit - Mixed success and errors: - value: - issues: - - userId: non-existent-user - level: error - reason: User does not exist - - userId: user-456 - level: warning - reason: Would exceed maximum freeze limit - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Create streak freezes for multiple users - security: - - ApiKeyAuth: [] - - /streaks/restore: - servers: - - url: https://admin.trophy.so/v1 - description: Admin API - post: - description: Restore streaks for multiple users to the maximum length in the last 90 days (in the case of daily streaks), one year (in the case of weekly streaks), or two years (in the case of monthly streaks). - operationId: admin_streaks_restore - x-fern-server-name: admin - x-fern-sdk-group-name: - - admin - - streaks - x-fern-sdk-method-name: restore - tags: - - Admin - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.admin.streaks.restore({ - users: [{ id: 'user-123' }, { id: 'user-456' }] - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.admin.streaks.restore({ - "users": [{"id": "user-123"}, {"id": "user-456"}] - }) - requestBody: - description: Array of users to restore streaks for - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/RestoreStreaksRequest' - examples: - Restore streaks for multiple users: - value: - users: - - id: user-123 - - id: user-456 - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/RestoreStreaksResponse' - examples: - Success with no issues: - value: - restoredUsers: - - user-123 - - user-456 - issues: [] - Success with warnings: - value: - restoredUsers: - - user-123 - issues: - - userId: user-456 - level: warning - reason: No streak to restore - Mixed success and errors: - value: - restoredUsers: - - user-123 - issues: - - userId: non-existent-user - level: error - reason: User does not exist - - userId: user-789 - level: warning - reason: Streak is already at maximum length - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Restore streaks for multiple users - security: - - ApiKeyAuth: [] - - /points/boosts: - servers: - - url: https://admin.trophy.so/v1 - description: Admin API - post: - description: Create points boosts for multiple users. - operationId: admin_points_boosts_create - x-fern-server-name: admin - x-fern-sdk-group-name: - - admin - - points - - boosts - x-fern-sdk-method-name: create - tags: - - Admin - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.admin.points.boosts.create({ - systemKey: 'xp', - boosts: [ - { - userId: 'user-123', - name: 'Double XP Weekend', - start: '2024-01-01', - end: '2024-01-03', - multiplier: 2 - }, - { - userId: 'user-456', - name: 'Holiday Bonus', - start: '2024-12-25', - multiplier: 1.5, - rounding: 'up' - } - ] - }); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.admin.points.boosts.create({ - "systemKey": "xp", - "boosts": [ - { - "userId": "user-123", - "name": "Double XP Weekend", - "start": "2024-01-01", - "end": "2024-01-03", - "multiplier": 2 - }, - { - "userId": "user-456", - "name": "Holiday Bonus", - "start": "2024-12-25", - "multiplier": 1.5, - "rounding": "up" - } - ] - }) - requestBody: - description: The points system key and array of boosts to create - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreatePointsBoostsRequest' - examples: - Create boosts for multiple users: - value: - systemKey: xp - boosts: - - userId: user-123 - name: Double XP Weekend - start: '2024-01-01' - end: '2024-01-03' - multiplier: 2 - - userId: user-456 - name: Holiday Bonus - start: '2024-12-25' - multiplier: 1.5 - rounding: up - responses: - '200': - description: Successful operation (no boosts created) - content: - application/json: - schema: - $ref: '#/components/schemas/CreatePointsBoostsResponse' - examples: - All requests had errors: - value: - created: [] - issues: - - userId: non-existent-user - level: error - reason: User does not exist - '201': - description: Created (at least one boost created) - content: - application/json: - schema: - $ref: '#/components/schemas/CreatePointsBoostsResponse' - examples: - Success with no issues: - value: - created: - - id: '550e8400-e29b-41d4-a716-446655440000' - name: Double XP Weekend - status: active - start: '2024-01-01' - end: '2024-01-03' - multiplier: 2 - rounding: down - userId: user-123 - issues: [] - Mixed success and errors: - value: - created: - - id: '550e8400-e29b-41d4-a716-446655440001' - name: Valid Boost - status: active - start: '2024-01-15' - end: null - multiplier: 1.5 - rounding: down - userId: user-456 - issues: - - userId: non-existent-user - level: error - reason: User does not exist - '400': - description: 'Bad Request' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found (points system not found)' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '422': - description: 'Unprocessible Entity' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Create points boosts for multiple users - security: - - ApiKeyAuth: [] - delete: - description: Archive multiple points boosts by ID. - operationId: admin_points_boosts_batch_archive - x-fern-server-name: admin - x-fern-sdk-group-name: - - admin - - points - - boosts - x-fern-sdk-method-name: batchArchive - tags: - - Admin - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - const response = await trophy.admin.points.boosts.batchArchive({ - ids: ['boost-uuid-1', 'boost-uuid-2', 'boost-uuid-3'] - }); - - console.log(`Archived ${response.archivedCount} boosts`); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - response = client.admin.points.boosts.batch_archive( - ids=['boost-uuid-1', 'boost-uuid-2', 'boost-uuid-3'] - ) - - print(f"Archived {response.archived_count} boosts") - parameters: - - name: ids - in: query - description: A list of up to 100 boost IDs. - required: true - schema: - type: array - items: - type: string - minItems: 1 - maxItems: 100 - example: 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,b1ffcd00-0d1c-4ef9-cc7e-7cc0ce491b22' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ArchivePointsBoostsResponse' - examples: - All boosts archived: - value: - archivedCount: 3 - Some boosts not found: - value: - archivedCount: 1 - No boosts found: - value: - archivedCount: 0 - '400': - description: 'Bad Request (no IDs provided or invalid UUID format)' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Archive multiple points boosts - security: - - ApiKeyAuth: [] - - /points/boosts/{id}: - servers: - - url: https://admin.trophy.so/v1 - description: Admin API - delete: - description: Archive a points boost by ID. - operationId: admin_points_boosts_archive - x-fern-server-name: admin - x-fern-sdk-group-name: - - admin - - points - - boosts - x-fern-sdk-method-name: archive - tags: - - Admin - x-codeSamples: - - lang: javascript - source: | - import { TrophyApiClient } from '@trophyso/node'; - - const trophy = new TrophyApiClient({ - apiKey: 'YOUR_API_KEY' - }); - - await trophy.admin.points.boosts.archive('boost-uuid-here'); - - lang: python - source: | - from trophy import TrophyApi - - client = TrophyApi(api_key='YOUR_API_KEY') - - client.admin.points.boosts.archive('boost-uuid-here') - parameters: - - name: id - in: path - required: true - description: The UUID of the points boost to archive - schema: - type: string - format: uuid - responses: - '204': - description: Successfully archived the points boost - '401': - description: 'Unauthorized' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - '404': - description: 'Not Found' - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorBody' - summary: Archive a points boost - security: - - ApiKeyAuth: [] - -webhooks: - achievement.completed: - post: - summary: Achievement completed - operationId: webhooks_achievement_completed - description: Triggered when a user completes an achievement. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['achievement.completed'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user who completed the achievement. - achievement: - $ref: '#/components/schemas/UserAchievementResponse' - description: The achievement completion that occurred. - required: - - type - - user - - achievement - examples: - Achievement completed: - value: - type: achievement.completed - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - achievement: - id: d01dcbcb-d51e-4c12-b054-dc811dcdc625 - name: Completed Onboarding - trigger: api - description: null - key: completed-onboarding - achievedAt: '2021-01-01T00:00:00Z' - badgeUrl: https://example.com/badge2.png - - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - streak.started: - post: - summary: Streak started - operationId: webhooks_streak_started - description: Triggered when a user starts a streak. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['streak.started'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user who started the streak. - streak: - $ref: '#/components/schemas/BaseStreakResponse' - description: The streak that was started. - required: - - type - - user - - streak - examples: - Streak started: - value: - type: streak.started - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - streak: - length: 1 - frequency: daily - periodStart: '2025-04-02' - periodEnd: '2025-04-02' - started: '2025-04-02' - expires: '2025-04-03' - freezes: 0 - maxFreezes: 3 - freezeAutoEarnInterval: 7 - freezeAutoEarnAmount: 1 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - streak.extended: - post: - summary: Streak extended - operationId: webhooks_streak_extended - description: Triggered when a user extends an existing active streak. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['streak.extended'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user who extended the streak. - streak: - $ref: '#/components/schemas/BaseStreakResponse' - description: The streak that was extended. - required: - - type - - user - - streak - examples: - Streak extended: - value: - type: streak.extended - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - streak: - length: 2 - frequency: daily - periodStart: '2025-04-03' - periodEnd: '2025-04-03' - started: '2025-04-02' - expires: '2025-04-05' - freezes: 0 - maxFreezes: 3 - freezeAutoEarnInterval: 7 - freezeAutoEarnAmount: 1 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - streak.lost: - post: - summary: Streak lost - operationId: webhooks_streak_lost - description: Triggered when a user loses their streak. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['streak.lost'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user who lost the streak. - length: - type: integer - description: The length of the streak that was lost. - required: - - type - - user - - length - examples: - Streak lost: - value: - type: streak.lost - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - length: 7 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - streak.freeze_consumed: - post: - summary: Streak freeze consumed - operationId: webhooks_streak_freeze_consumed - description: Triggered when a user consumes a streak freeze. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['streak.freeze_consumed'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user whose streak freeze was consumed. - consumed: - type: integer - description: The number of freezes consumed. - freezes: - type: integer - description: The total number of freezes the user has left after the consumption. - required: - - type - - user - - consumed - - freezes - examples: - Streak freeze consumed: - value: - type: streak.freeze_consumed - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - consumed: 1 - freezes: 2 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - streak.freeze_earned: - post: - summary: Streak freeze earned - operationId: webhooks_streak_freeze_earned - description: Triggered when a user earns streak freezes. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['streak.freeze_earned'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user who earned streak freezes. - earned: - type: integer - description: The number of freezes earned. - freezes: - type: integer - description: The total number of freezes the user has after the event. - required: - - type - - user - - earned - - freezes - examples: - Streak freeze earned: - value: - type: streak.freeze_earned - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - earned: 1 - freezes: 2 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - points.changed: - post: - summary: Points changed - operationId: webhooks_points_changed - description: Triggered when a user is awarded or loses points. This event is fired a maximum of once per user per points system per minute. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['points.changed'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user whose points increased or decreased. - points: - $ref: '#/components/schemas/MetricEventPointsResponse' - description: The user's points after the event (includes added amount for this event). - required: - - type - - user - - points - examples: - Points changed: - value: - type: points.changed - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - points: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - key: xp - name: XP - description: null - badgeUrl: null - maxPoints: null - total: 100 - added: 10 - awards: - - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - awarded: 10 - date: '2021-01-01T00:00:00Z' - total: 100 - trigger: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - type: metric - points: 10 - metricName: words written - metricThreshold: 1000 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - points.level_changed: - post: - summary: Points level changed - operationId: webhooks_points_level_changed - description: Triggered when a user's level changes within a points system as a result of earning or losing points. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['points.level_changed'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user whose level changed. - points: - description: The points system in which the level changed. - allOf: - - $ref: '#/components/schemas/PointsResponse' - - type: object - properties: - total: - type: integer - description: The user's total points in this system. - required: - - total - previousLevel: - description: The user's previous level, or null if the user had no level. - oneOf: - - $ref: '#/components/schemas/PointsLevel' - - type: 'null' - newLevel: - description: The user's new level, or null if the user no longer has a level. - oneOf: - - $ref: '#/components/schemas/PointsLevel' - - type: 'null' - required: - - type - - user - - points - - previousLevel - - newLevel - examples: - Level changed: - value: - type: points.level_changed - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - points: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - key: xp - name: XP - description: Experience points - badgeUrl: null - maxPoints: null - total: 100 - previousLevel: - id: 1140fe51-6bce-4b44-b0ad-bddc4e123534 - key: bronze - name: Bronze - description: Starting level - badgeUrl: https://example.com/bronze.png - points: 0 - newLevel: - id: 2240fe51-6bce-4b44-b0ad-bddc4e123534 - key: silver - name: Silver - description: Mid-tier level - badgeUrl: null - points: 50 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - points.boost_started: - post: - summary: Points boost started - operationId: webhooks_points_boost_started - description: Triggered when a points boost goes live (its start time has been reached). - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['points.boost_started'] - description: The webhook event type. - timestamp: - type: string - format: date-time - description: When the event occurred (ISO 8601). - boost: - $ref: '#/components/schemas/PointsBoostWebhookPayload' - description: The points boost that started. - required: - - type - - timestamp - - boost - examples: - Points boost started: - value: - type: points.boost_started - timestamp: '2025-01-15T00:00:00Z' - boost: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - name: Double XP Weekend - status: active - userId: null - pointsSystemId: 0040fe51-6bce-4b44-b0ad-bddc4e123535 - pointsSystemKey: xp - pointsSystemName: XP - start: '2025-01-15' - end: '2025-01-17' - multiplier: 2 - rounding: down - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - points.boost_finished: - post: - summary: Points boost finished - operationId: webhooks_points_boost_finished - description: Triggered when a points boost ends (its end time has been reached). - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['points.boost_finished'] - description: The webhook event type. - timestamp: - type: string - format: date-time - description: When the event occurred (ISO 8601). - boost: - $ref: '#/components/schemas/PointsBoostWebhookPayload' - description: The points boost that finished. - required: - - type - - timestamp - - boost - examples: - Points boost finished: - value: - type: points.boost_finished - timestamp: '2025-01-17T23:59:59Z' - boost: - id: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - name: Double XP Weekend - status: finished - userId: null - pointsSystemId: 0040fe51-6bce-4b44-b0ad-bddc4e123535 - pointsSystemKey: xp - pointsSystemName: XP - start: '2025-01-15' - end: '2025-01-17' - multiplier: 2 - rounding: down - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - leaderboard.started: - post: - summary: Leaderboard started - operationId: webhooks_leaderboard_started - description: Triggered when a run of a leaderboard begins. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['leaderboard.started'] - description: The webhook event type. - leaderboard: - $ref: '#/components/schemas/LeaderboardResponseWithRankings' - description: The leaderboard run that started and its initial rankings. - required: - - type - - leaderboard - examples: - Leaderboard started: - value: - type: leaderboard.started - leaderboard: - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: Weekly Word Count Challenge - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - pointsSystemKey: null - pointsSystemName: null - description: Compete weekly to see who writes the most words - status: active - start: '2025-01-01' - end: null - maxParticipants: 100 - breakdownAttribute: null - runUnit: day - runInterval: 7 - rankings: - - userId: user-123 - userName: Alice Johnson - rank: 1 - value: 1 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - leaderboard.changed: - post: - summary: Leaderboard changed - operationId: webhooks_leaderboard_changed - description: Triggered when leaderboard rankings change. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['leaderboard.changed'] - description: The webhook event type. - leaderboard: - $ref: '#/components/schemas/LeaderboardResponseWithRankings' - description: The leaderboard run that changed. - required: - - type - - leaderboard - examples: - Leaderboard changed: - value: - type: leaderboard.changed - leaderboard: - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: Weekly Word Count Challenge - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - pointsSystemKey: null - pointsSystemName: null - description: Compete weekly to see who writes the most words - status: active - start: '2025-01-01' - end: null - maxParticipants: 100 - breakdownAttribute: null - runUnit: day - runInterval: 7 - rankings: - - userId: user-123 - userName: Alice Johnson - rank: 1 - value: 10 - - userId: user-456 - userName: Bob Smith - rank: 2 - value: 6 - - userId: user-789 - userName: Charlie Brown - rank: 3 - value: 4 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - leaderboard.finished: - post: - summary: Leaderboard finished - operationId: webhooks_leaderboard_finished - description: Triggered when a run of a leaderboard finishes. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['leaderboard.finished'] - description: The webhook event type. - leaderboard: - $ref: '#/components/schemas/LeaderboardResponseWithRankings' - description: The leaderboard run that finished and its final rankings. - required: - - type - - leaderboard - examples: - Leaderboard finished: - value: - type: leaderboard.finished - leaderboard: - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: Weekly Word Count Challenge - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - pointsSystemKey: null - pointsSystemName: null - description: Compete weekly to see who writes the most words - status: active - start: '2025-01-01' - end: null - maxParticipants: 100 - breakdownAttribute: null - runUnit: day - runInterval: 7 - rankings: - - userId: user-123 - userName: Alice Johnson - rank: 1 - value: 10 - - userId: user-456 - userName: Bob Smith - rank: 2 - value: 6 - - userId: user-789 - userName: Charlie Brown - rank: 3 - value: 4 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. - leaderboard.rank_changed: - post: - summary: Leaderboard rank changed - operationId: webhooks_leaderboard_rank_changed - description: Triggered when a user's leaderboard rank changes. - requestBody: - description: The webhook event. - content: - application/json: - schema: - properties: - type: - type: string - enum: ['leaderboard.rank_changed'] - description: The webhook event type. - user: - $ref: '#/components/schemas/User' - description: The user whose rank changed. - leaderboard: - $ref: '#/components/schemas/WebhookUserLeaderboardResponse' - description: The user's leaderboard data that changed. - required: - - type - - user - - leaderboard - examples: - Leaderboard rank changed: - value: - type: leaderboard.rank_changed - user: - id: user-id - email: user@example.com - tz: Europe/London - subscribedToEmails: true - created: '2021-01-01T00:00:00Z' - updated: '2021-01-01T00:00:00Z' - attributes: - department: engineering - role: developer - leaderboard: - id: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: Weekly Word Count Challenge - key: weekly-words - rankBy: metric - metricKey: words-written - metricName: Words Written - description: Compete weekly to see who writes the most words - start: '2025-01-01' - end: null - maxParticipants: 100 - breakdownAttribute: null - runUnit: day - runInterval: 7 - rank: 2 - value: 4500 - previousRank: 1 - previousValue: 4500 - responses: - '200': - description: Return a 200 status to indicate the webhook was received and processed. -components: - schemas: - MetricStatus: - title: MetricStatus - type: string - enum: - - archived - - active - description: The status of the achievement. - StreakFrequency: - title: StreakFrequency - type: string - enum: - - daily - - weekly - - monthly - BaseStreakResponse: - title: Streak Response - type: object - properties: - length: - type: integer - description: The length of the user's current streak. - frequency: - $ref: '#/components/schemas/StreakFrequency' - description: The frequency of the streak. - started: - type: - - string - - 'null' - format: date - description: The date the streak started. - periodStart: - type: - - string - - 'null' - format: date - description: The start date of the current streak period. - periodEnd: - type: - - string - - 'null' - format: date - description: The end date of the current streak period. - expires: - type: - - string - - 'null' - format: date - description: The date the streak will expire if the user does not increment a metric. - freezes: - type: integer - description: The number of available streak freezes. Only present if the organization has enabled streak freezes. - maxFreezes: - type: integer - description: The maximum number of streak freezes a user can have. Only present if the organization has enabled streak freezes. - freezeAutoEarnInterval: - type: integer - description: The interval at which the user will earn streak freezes, in days. Only present if the organization has enabled streak freeze auto-earn. - freezeAutoEarnAmount: - type: integer - description: The amount of streak freezes the user will earn per interval. Only present if the organization has enabled streak freeze auto-earn. - required: - - length - - frequency - - started - - periodStart - - periodEnd - - expires - BulkStreakResponse: - title: Bulk Streak Response - type: array - items: - properties: - userId: - type: string - description: The ID of the user. - streakLength: - type: integer - description: The length of the user's streak. - extended: - type: - - string - - 'null' - description: The timestamp the streak was extended, as a string. Null if the streak is not active. - required: - - userId - - streakLength - - extended - MetricEventStreakResponse: - title: Streak Response (Metric Event) - type: object - description: An object representing the user's streak after sending a metric event. - allOf: - - $ref: '#/components/schemas/BaseStreakResponse' - - type: object - properties: - extended: - type: boolean - description: Whether this metric event increased the user's streak length. - required: - - extended - StreakResponse: - title: Streak Response - type: object - description: An object representing the user's streak. - allOf: - - $ref: '#/components/schemas/BaseStreakResponse' - - type: object - properties: - streakHistory: - type: array - description: >- - A list of the user's past streak periods up through the current period. Each - period includes the start and end dates and the length of the streak. - items: - type: object - description: >- - An object representing a past streak period. - properties: - periodStart: - type: string - format: date - description: The date this streak period started. - example: '2025-03-31' - periodEnd: - type: string - format: date - description: The date this streak period ended. - example: '2025-04-05' - length: - type: integer - description: The length of the user's streak during this period. - example: 1 - usedFreeze: - type: boolean - description: Whether the user used a streak freeze during this period. Only present if the organization has enabled streak freezes. - example: false - required: - - periodStart - - periodEnd - - length - rank: - type: - - integer - - 'null' - description: The user's rank across all users. Null if the user has no active streak. - example: 5 - required: - - rank - PointsTrigger: - title: PointsTrigger - type: object - properties: - id: - type: string - description: The ID of the trigger - type: - type: string - description: The type of trigger - enum: ['metric', 'achievement', 'streak', 'time', 'user_creation'] - points: - type: integer - description: The points awarded by this trigger. - metricName: - type: string - description: If the trigger has type 'metric', the name of the metric - metricThreshold: - type: integer - description: If the trigger has type 'metric', the threshold of the metric that triggers the points - streakLengthThreshold: - type: integer - description: If the trigger has type 'streak', the threshold of the streak that triggers the points - achievementName: - type: string - description: If the trigger has type 'achievement', the name of the achievement - timeUnit: - type: string - enum: ['hour', 'day'] - description: If the trigger has type 'time', the unit of time after which to award points - timeInterval: - type: integer - description: If the trigger has type 'time', the numer of units of timeUnit after which to award points - PointsAward: - title: PointsAward - type: object - properties: - id: - type: string - description: The ID of the trigger award - awarded: - type: integer - description: The points awarded by this trigger - date: - type: string - description: The date these points were awarded, in ISO 8601 format. - total: - type: integer - description: The user's total points after this award occurred. - trigger: - $ref: '#/components/schemas/PointsTrigger' - boosts: - type: array - description: Array of points boosts that applied to this award. - items: - $ref: '#/components/schemas/PointsBoost' - PointsBoost: - title: PointsBoost - type: object - properties: - id: - type: string - description: The ID of the points boost - name: - type: string - description: The name of the points boost - status: - type: string - enum: ['active', 'scheduled', 'finished'] - description: The status of the points boost - start: - type: string - description: The start date of the points boost - end: - type: - - string - - 'null' - description: The end date of the points boost - multiplier: - type: number - description: The multiplier of the points boost - rounding: - type: string - enum: ['down', 'up', 'nearest'] - description: The rounding method of the points boost - required: - - id - - name - - status - - start - - end - - multiplier - - rounding - PointsBoostWebhookPayload: - title: PointsBoostWebhookPayload - type: object - description: Points boost payload sent in points.boost_started and points.boost_finished webhook events. - properties: - id: - type: string - description: The ID of the points boost. - name: - type: string - description: The name of the points boost. - status: - type: string - enum: ['active', 'finished'] - description: The status of the points boost. - userId: - type: - - string - - 'null' - description: The customer-facing user ID that the boost is scoped to, or null for global boosts. - pointsSystemId: - type: string - description: The ID of the points system this boost applies to. - pointsSystemKey: - type: string - description: The key of the points system this boost applies to. - pointsSystemName: - type: string - description: The name of the points system this boost applies to. - start: - type: string - format: date - description: The start date of the points boost (YYYY-MM-DD). - end: - type: - - string - - 'null' - format: date - description: The end date of the points boost (YYYY-MM-DD), or null if open-ended. - multiplier: - type: number - description: The multiplier applied to points during the boost. - rounding: - type: string - enum: ['down', 'up', 'nearest'] - description: The rounding method applied to boosted points. - required: - - id - - name - - status - - userId - - pointsSystemId - - pointsSystemKey - - pointsSystemName - - start - - end - - multiplier - - rounding - PointsResponse: - title: PointsResponse - type: object - description: Base points system fields shared across responses. - properties: - id: - type: string - description: The ID of the points system - key: - type: string - description: The key of the points system - name: - type: string - description: The name of the points system - description: - type: - - string - - 'null' - description: The description of the points system - badgeUrl: - type: - - string - - 'null' - description: The URL of the badge image for the points system - maxPoints: - type: - - number - - 'null' - description: The maximum number of points a user can be awarded in this points system - required: - - id - - key - - name - - description - - badgeUrl - - maxPoints - GetUserPointsResponse: - title: GetUserPointsResponse - type: object - allOf: - - $ref: '#/components/schemas/PointsResponse' - - type: object - properties: - total: - type: integer - description: The user's total points - level: - description: The user's current level in this points system, or null if no levels are configured or the user hasn't reached any level yet. - oneOf: - - $ref: '#/components/schemas/PointsLevel' - - type: 'null' - awards: - type: array - description: Array of trigger awards that added points. - items: - $ref: '#/components/schemas/PointsAward' - required: - - total - - level - - awards - PointsLevelSummaryResponse: - title: PointsLevelSummaryResponse - type: array - description: A breakdown of users by level in a points system. - items: - type: object - properties: - level: - $ref: '#/components/schemas/PointsLevel' - users: - type: integer - description: The number of users currently at this level - required: - - level - - users - PointsLevel: - title: PointsLevel - type: object - description: A level within a points system. - properties: - id: - type: string - description: The ID of the level - key: - type: string - description: The unique key of the level - name: - type: string - description: The name of the level - description: - type: string - description: The description of the level - badgeUrl: - type: - - string - - 'null' - description: The URL of the badge image for the level - points: - type: integer - description: The points threshold required to reach this level - required: - - id - - key - - name - - description - - badgeUrl - - points - LeaderboardResponse: - title: LeaderboardResponse - type: object - description: A leaderboard with its configuration details. - properties: - id: - type: string - description: The unique ID of the leaderboard. - example: 5100fe51-6bce-6j44-b0hs-bddc4e123682 - name: - type: string - description: The user-facing name of the leaderboard. - example: Weekly Word Count Challenge - key: - type: string - description: The unique key used to reference the leaderboard in APIs. - example: weekly-words - rankBy: - type: string - enum: ['points', 'streak', 'metric'] - description: What the leaderboard ranks by. - example: metric - breakdownAttribute: - type: - - string - - 'null' - description: The key of the attribute to break down this leaderboard by. - example: country - metricKey: - type: string - description: The key of the metric to rank by, if rankBy is 'metric'. - example: words-written - metricName: - type: string - description: The name of the metric to rank by, if rankBy is 'metric'. - example: Words Written - pointsSystemKey: - type: string - description: The key of the points system to rank by, if rankBy is 'points'. - example: xp-system - pointsSystemName: - type: string - description: The name of the points system to rank by, if rankBy is 'points'. - example: Experience Points - description: - type: - - string - - 'null' - description: The user-facing description of the leaderboard. - example: Compete weekly to see who writes the most words - start: - type: string - format: date - description: The start date of the leaderboard in YYYY-MM-DD format. - example: '2025-01-01' - end: - type: - - string - - 'null' - format: date - description: The end date of the leaderboard in YYYY-MM-DD format, or null if it runs forever. - example: '2025-12-31' - maxParticipants: - type: integer - description: The maximum number of participants in the leaderboard. - example: 100 - runUnit: - type: - - string - - 'null' - enum: ['day', 'month', 'year', null] - description: The repetition type for recurring leaderboards, or null for one-time leaderboards. - example: day - runInterval: - type: - - integer - - 'null' - description: The interval between repetitions, relative to the start date and repetition type. Null for one-time leaderboards. - example: 7 - required: - - id - - name - - key - - status - - description - - rankBy - - breakdownAttribute - - start - - end - - maxParticipants - - runUnit - - runInterval - LeaderboardResponseWithRankings: - title: LeaderboardResponseWithRankings - type: object - allOf: - - $ref: '#/components/schemas/LeaderboardResponse' - - type: object - properties: - status: - type: string - enum: ['active', 'scheduled', 'finished'] - description: The status of the leaderboard. - example: active - rankings: - type: array - description: Array of user rankings for the leaderboard. - items: - $ref: '#/components/schemas/LeaderboardRanking' - required: - - rankings - - status - MetricEventPointsResponse: - title: MetricEventPointsResponse - type: object - description: Points system response for metric events and achievement completions. - allOf: - - $ref: '#/components/schemas/PointsResponse' - - type: object - properties: - total: - type: integer - description: The user's total points - level: - description: The user's new level, included only when the level changed as a result of this event. - oneOf: - - $ref: '#/components/schemas/PointsLevel' - - type: 'null' - added: - type: integer - description: The points added by this event. - example: 10 - awards: - type: array - description: Array of trigger awards that added points. - items: - $ref: '#/components/schemas/PointsAward' - required: - - total - - added - - awards - MetricEventLeaderboardResponse: - title: MetricEventLeaderboardResponse - type: object - allOf: - - $ref: '#/components/schemas/LeaderboardResponse' - - type: object - properties: - start: - type: string - format: date - description: The start date of the current run of the leaderboard. - example: '2025-01-01' - end: - type: - - string - - 'null' - format: date - description: The end date of the current run of the leaderboard, or null if the run never ends. - example: '2025-12-31' - rank: - type: - - integer - - 'null' - description: The user's rank in the leaderboard, or null if the user is not on the leaderboard. - example: 100 - previousRank: - type: - - integer - - 'null' - description: The user's rank in the leaderboard before the event, or null if the user was not on the leaderboard before the event. - example: 100 - threshold: - type: integer - description: The minimum value required to enter the leaderboard according to its current rankings. - example: 25000 - breakdownAttributeValue: - type: string - description: For leaderboards with a breakdown attribute, the value of the attribute for the user. - example: USA - required: - - start - - end - - threshold - - rank - - previousRank - AchievementResponse: - title: AchievementResponse - type: object - properties: - id: - type: string - description: The unique ID of the achievement. - name: - type: string - description: The name of this achievement. - trigger: - type: string - enum: ['metric', 'streak', 'api', 'achievement'] - description: The trigger of the achievement. - description: - type: - - string - - 'null' - description: The description of this achievement. - badgeUrl: - type: - - string - - 'null' - description: >- - The URL of the badge image for the achievement, if one has been - uploaded. - key: - type: string - description: The key used to reference this achievement in the API (only applicable if trigger = 'api') - streakLength: - type: integer - description: The length of the streak required to complete the achievement (only applicable if trigger = 'streak') - achievementIds: - type: array - items: - type: string - description: The IDs of the prerequisite achievements that must be completed to earn this achievement (only applicable if trigger = 'achievement') - metricId: - type: string - description: The ID of the metric associated with this achievement (only applicable if trigger = 'metric') - metricValue: - type: number - format: double - description: >- - The value of the metric required to complete the achievement (only applicable if trigger = 'metric') - metricName: - type: string - description: The name of the metric associated with this achievement (only applicable if trigger = 'metric') - userAttributes: - type: array - description: User attribute filters that must be met for this achievement to be completed. Only present if the achievement has user attribute filters configured. - items: - type: object - properties: - key: - type: string - description: The key of the user attribute. - example: plan-type - value: - type: string - description: The value of the user attribute. - example: premium - required: - - key - - value - eventAttribute: - type: object - description: Event attribute filter that must be met for this achievement to be completed. Only present if the achievement has an event filter configured. - properties: - key: - type: string - description: The key of the event attribute. - example: source - value: - type: string - description: The value of the event attribute. - example: mobile-app - required: - - key - - value - required: - - id - - name - - trigger - - description - - badgeUrl - UserAchievementResponse: - title: UserAchievementResponse - type: object - allOf: - - $ref: '#/components/schemas/AchievementResponse' - - type: object - properties: - achievedAt: - type: - - string - - 'null' - format: date-time - description: The date and time the achievement was completed, in ISO 8601 format. Null if the achievement has not been completed. - required: - - achievedAt - UserAchievementWithStatsResponse: - title: UserAchievementWithStatsResponse - type: object - allOf: - - $ref: '#/components/schemas/AchievementWithStatsResponse' - - type: object - properties: - achievedAt: - type: - - string - - 'null' - format: date-time - description: The date and time the achievement was completed, in ISO 8601 format. Null if the achievement has not been completed. - required: - - achievedAt - AchievementWithStatsResponse: - title: AchievementWithStatsResponse - type: object - allOf: - - $ref: '#/components/schemas/AchievementResponse' - - type: object - properties: - completions: - type: integer - description: The number of users who have completed this achievement. - rarity: - type: number - format: double - description: The percentage of all users who have completed this achievement. - required: - - completions - - rarity - MetricResponse: - title: MetricResponse - type: object - properties: - id: - type: string - description: The unique ID of the metric. - example: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - key: - type: string - description: The unique key of the metric. - example: words-written - name: - type: string - description: The name of the metric. - example: Words written - status: - $ref: '#/components/schemas/MetricStatus' - description: The status of the metric. - current: - type: number - format: double - description: The user's current total for the metric. - example: 1500 - achievements: - type: array - items: - $ref: '#/components/schemas/UserAchievementResponse' - description: >- - A list of the metric's achievements and the user's progress towards - each. - required: - - id - - key - - name - - status - - current - - achievements - UpdatedUser: - title: Updated User - type: object - description: An object with editable user fields. - properties: - email: - type: string - description: The user's email address. Required if subscribeToEmails is true. - example: user@example.com - name: - type: string - description: The name to refer to the user by in emails. - example: User - tz: - type: - - string - - 'null' - description: The user's timezone (used for email scheduling). - example: Europe/London - deviceTokens: - type: - - array - - 'null' - description: The user's device tokens, used for push notifications. - items: - type: string - description: The device token. - example: ['token1', 'token2'] - subscribeToEmails: - type: boolean - default: true - description: Whether the user should receive Trophy-powered emails. If false, Trophy will not store the user's email address. - example: true - attributes: - type: object - additionalProperties: - type: string - description: User attributes as key-value pairs. Keys must match existing user attributes set up in the Trophy dashboard. - example: - department: engineering - role: developer - UpsertedUser: - title: Upserted User - type: object - description: An object with editable user fields. - allOf: - - $ref: '#/components/schemas/UpdatedUser' - - type: object - properties: - id: - type: string - description: The ID of the user in your database. Must be a string. - example: user-id - required: - - id - User: - title: User - type: object - description: A user of your application. - properties: - id: - type: string - description: The ID of the user in your database. Must be a string. - example: user-id - email: - type: - - string - - 'null' - description: The user's email address. - example: user@example.com - name: - type: - - string - - 'null' - description: The name of the user. - example: John Doe - tz: - type: - - string - - 'null' - description: The user's timezone. - example: Europe/London - deviceTokens: - type: - - array - - 'null' - description: The user's device tokens. - items: - type: string - description: The device token. - example: ['token1', 'token2'] - subscribeToEmails: - type: boolean - description: Whether the user is opted into receiving Trophy-powered emails. - example: true - attributes: - type: object - additionalProperties: - type: string - description: User attributes as key-value pairs. Keys must match existing user attributes set up in the Trophy dashboard. - example: - department: engineering - role: developer - control: - type: boolean - description: Whether the user is in the control group, meaning they do not receive emails or other communications from Trophy. - example: false - created: - type: string - format: date-time - description: The date and time the user was created, in ISO 8601 format. - example: '2021-01-01T00:00:00Z' - updated: - type: string - format: date-time - description: The date and time the user was last updated, in ISO 8601 format. - example: '2021-01-01T00:00:00Z' - required: - - id - - email - - name - - tz - - subscribeToEmails - - attributes - - control - - created - - updated - NotificationChannel: - title: NotificationChannel - type: string - enum: - - email - - push - description: A notification delivery channel. - NotificationType: - title: NotificationType - type: string - enum: - - achievement_completed - - recap - - reactivation - - streak_reminder - description: A type of notification that can be configured. - NotificationPreferences: - title: NotificationPreferences - type: object - description: Notification preferences for each notification type. - properties: - achievement_completed: - type: array - items: - $ref: '#/components/schemas/NotificationChannel' - description: Channels to receive achievement completion notifications on. - recap: - type: array - items: - $ref: '#/components/schemas/NotificationChannel' - description: Channels to receive recap notifications on. - reactivation: - type: array - items: - $ref: '#/components/schemas/NotificationChannel' - description: Channels to receive reactivation notifications on. - streak_reminder: - type: array - items: - $ref: '#/components/schemas/NotificationChannel' - description: Channels to receive streak reminder notifications on. - UserPreferencesResponse: - title: UserPreferencesResponse - type: object - description: A user's preferences. - properties: - notifications: - $ref: '#/components/schemas/NotificationPreferences' - required: - - notifications - UpdateUserPreferencesRequest: - title: UpdateUserPreferencesRequest - type: object - description: Request body for updating user preferences. - properties: - notifications: - $ref: '#/components/schemas/NotificationPreferences' - ErrorBody: - title: ErrorBody - type: object - properties: - error: - type: string - required: - - error - AchievementCompletionResponse: - title: AchievementCompletionResponse - type: object - properties: - completionId: - type: string - description: The unique ID of the completion. - example: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - achievement: - $ref: '#/components/schemas/UserAchievementResponse' - points: - type: object - additionalProperties: - $ref: '#/components/schemas/MetricEventPointsResponse' - description: >- - A map of points systems by key that were affected by this achievement completion. - required: - - completionId - - achievement - - points - EventResponse: - title: EventResponse - type: object - properties: - eventId: - type: string - description: The unique ID of the event. - example: 0040fe51-6bce-4b44-b0ad-bddc4e123534 - metricId: - type: string - description: The unique ID of the metric that was updated. - example: d01dcbcb-d51e-4c12-b054-dc811dcdc623 - total: - type: number - format: double - description: The user's new total progress against the metric. - example: 750 - achievements: - type: array - items: - $ref: '#/components/schemas/UserAchievementResponse' - description: Achievements completed as a result of this event. - currentStreak: - $ref: '#/components/schemas/MetricEventStreakResponse' - description: >- - The user's current streak. - points: - type: object - additionalProperties: - $ref: '#/components/schemas/MetricEventPointsResponse' - description: >- - A map of points systems by key. Only contains points systems that were affected by the event. - leaderboards: - type: object - additionalProperties: - $ref: '#/components/schemas/MetricEventLeaderboardResponse' - description: >- - A map of leaderboards by key. Only contains leaderboards that were affected by the event. - idempotencyKey: - type: string - description: The idempotency key used for the event, if one was provided. - idempotentReplayed: - type: boolean - description: Whether the event was replayed due to idempotency. - required: - - eventId - - metricId - - total - - achievements - - currentStreak - - points - - leaderboards - PointsRange: - title: PointsRange - type: object - properties: - from: - type: integer - description: The start of the points range. Inclusive. - to: - type: integer - description: The end of the points range. Inclusive. - users: - type: integer - description: The number of users in this points range. - required: - - from - - to - - users - PointsSummaryResponse: - title: PointsSummaryResponse - type: array - description: >- - A list of eleven points ranges, with the first starting and ending at 0, - and the remaining 10 being calculated as 10 equally sized ranges from 1 - to the greatest number of points a user has, rounded up to the nearest - power of 10. - items: - $ref: '#/components/schemas/PointsRange' - PointsTriggerResponse: - title: PointsTriggerResponse - type: object - properties: - id: - type: string - description: The unique ID of the trigger. - type: - type: string - enum: ['metric', 'achievement', 'streak', 'time', 'user_creation'] - description: The type of trigger. - points: - type: integer - description: The points awarded by this trigger. - status: - type: string - enum: ['active', 'archived'] - description: The status of the trigger. - achievementId: - type: string - description: The unique ID of the achievement associated with this trigger, if the trigger is an achievement. - metricId: - type: string - description: The unique ID of the metric associated with this trigger, if the trigger is a metric. - metricThreshold: - type: integer - description: The amount that a user must increase the metric to earn the points, if the trigger is a metric. - streakLengthThreshold: - type: integer - description: The number of consecutive streak periods that a user must complete to earn the points, if the trigger is a streak. - metricName: - type: string - description: The name of the metric associated with this trigger, if the trigger is a metric. - achievementName: - type: string - description: The name of the achievement associated with this trigger, if the trigger is an achievement. - timeUnit: - type: string - enum: ['hour', 'day'] - description: The time unit of the trigger, if the trigger is a time interval. - timeInterval: - type: integer - description: The interval of the trigger in the time unit, if the trigger is a time interval. - userAttributes: - type: array - description: User attribute filters that must be met for this trigger to activate. Only present if the trigger has user attribute filters configured. - items: - type: object - properties: - key: - type: string - description: The key of the user attribute. - example: plan-type - value: - type: string - description: The value of the user attribute. - example: premium - required: - - key - - value - eventAttribute: - type: object - description: Event attribute filter that must be met for this trigger to activate. Only present if the trigger has an event filter configured. - properties: - key: - type: string - description: The key of the event attribute. - example: source - value: - type: string - description: The required value of the event attribute. - example: mobile-app - required: - - key - - value - created: - type: string - format: date-time - description: The date and time the trigger was created, in ISO 8601 format. - updated: - type: string - format: date-time - description: The date and time the trigger was last updated, in ISO 8601 format. - required: - - id - - type - - points - - status - - created - - updated - PointsSystemResponse: - title: PointsSystemResponse - type: object - properties: - id: - type: string - description: The unique ID of the points system. - name: - type: string - description: The name of the points system. - description: - type: - - string - - 'null' - description: The description of the points system. - badgeUrl: - type: - - string - - 'null' - description: The URL of the badge image for the points system, if one has been uploaded. - maxPoints: - type: - - number - - 'null' - description: The maximum number of points a user can be awarded in this points system - triggers: - type: array - description: Array of active triggers for this points system. - items: - $ref: '#/components/schemas/PointsTriggerResponse' - required: - - id - - name - - description - - badgeUrl - - maxPoints - - triggers - StreakRankingUser: - title: StreakRankingUser - type: object - description: A user with their streak length in the rankings. - properties: - userId: - type: string - description: The ID of the user. - example: user-123 - name: - type: - - string - - 'null' - description: The name of the user. May be null if no name is set. - example: Alice Johnson - streakLength: - type: integer - description: The user's streak length (active or longest depending on query parameter). - example: 15 - required: - - userId - - name - - streakLength - LeaderboardRanking: - title: LeaderboardRanking - type: object - description: A user's ranking in a leaderboard. - properties: - userId: - type: string - description: The ID of the user. - example: user-123 - userName: - type: - - string - - 'null' - description: The name of the user. May be null if no name is set. - example: Alice Johnson - rank: - type: integer - description: The user's rank in the leaderboard. - example: 1 - value: - type: integer - description: The user's value for this leaderboard (points, metric value, etc.). - example: 5000 - required: - - userId - - userName - - rank - - value - LeaderboardEvent: - title: LeaderboardEvent - type: object - description: A leaderboard event representing a change in a user's rank or value. - properties: - timestamp: - type: string - format: date-time - description: The timestamp when the event occurred. - example: '2025-01-15T10:30:00Z' - previousRank: - type: - - integer - - 'null' - description: The user's rank before this event, or null if they were not on the leaderboard. - example: 5 - rank: - type: - - integer - - 'null' - description: The user's rank after this event, or null if they are no longer on the leaderboard. - example: 3 - previousValue: - type: - - integer - - 'null' - description: The user's value before this event, or null if they were not on the leaderboard. - example: 1000 - value: - type: - - integer - - 'null' - description: The user's value after this event, or null if they are no longer on the leaderboard. - example: 3000 - required: - - time - - previousRank - - rank - - previousValue - - value - UserLeaderboardResponse: - title: UserLeaderboardResponse - type: object - description: A user's data for a specific leaderboard including rank, value, and history. - allOf: - - $ref: '#/components/schemas/LeaderboardResponse' - - type: object - properties: - rank: - type: - - integer - - 'null' - description: The user's current rank in this leaderboard. Null if the user is not on the leaderboard. - example: 2 - value: - type: - - integer - - 'null' - description: The user's current value in this leaderboard. Null if the user is not on the leaderboard. - example: 4500 - required: - - rank - - value - UserLeaderboardResponseWithHistory: - title: UserLeaderboardResponseWithHistory - type: object - description: A user's data for a specific leaderboard including rank, value, and history. - allOf: - - $ref: '#/components/schemas/UserLeaderboardResponse' - - type: object - properties: - history: - type: array - items: - $ref: '#/components/schemas/LeaderboardEvent' - description: An array of events showing the user's rank and value changes over time. - required: - - history - WebhookUserLeaderboardResponse: - title: WebhookUserLeaderboardResponse - type: object - description: A user's data for a specific leaderboard including rank, value, and history. - allOf: - - $ref: '#/components/schemas/UserLeaderboardResponse' - - type: object - properties: - previousRank: - type: - - integer - - 'null' - description: The user's rank before this event, or null if they were not on the leaderboard. - example: 5 - previousValue: - type: - - integer - - 'null' - description: The user's value before this event, or null if they were not on the leaderboard. - example: 1000 - required: - - previousRank - - previousValue - CreateStreakFreezesRequest: - title: CreateStreakFreezesRequest - type: object - description: Request body for creating streak freezes. - properties: - freezes: - type: array - items: - type: object - properties: - userId: - type: string - description: The ID of the user to create a freeze for. - example: user-123 - required: - - userId - description: Array of freezes to create. Maximum 1,000 freezes per request. - maxItems: 1000 - minItems: 1 - required: - - freezes - CreateStreakFreezesResponse: - title: CreateStreakFreezesResponse - type: object - description: Response containing any issues encountered while creating streak freezes. - properties: - issues: - type: array - items: - $ref: '#/components/schemas/BulkInsertIssue' - description: Array of issues encountered during freeze creation. - required: - - issues - CreatePointsBoostsRequest: - title: CreatePointsBoostsRequest - type: object - description: Request body for creating points boosts. - properties: - systemKey: - type: string - description: The key of the points system to create boosts for. - example: xp - boosts: - type: array - items: - type: object - properties: - userId: - type: string - description: The ID of the user to create a boost for. - example: user-123 - name: - type: string - description: The name of the boost. - maxLength: 255 - example: Double XP Weekend - start: - type: string - format: date - description: The start date of the boost (YYYY-MM-DD). - example: '2024-01-01' - end: - type: - - string - - 'null' - format: date - description: The end date of the boost (YYYY-MM-DD). If null, the boost has no end date. - example: '2024-01-03' - multiplier: - type: number - description: The points multiplier. Must be greater than 0, not equal to 1, and less than 100. - example: 2 - exclusiveMinimum: 0 - exclusiveMaximum: 100 - rounding: - type: string - enum: - - down - - up - - nearest - default: down - description: How to round the boosted points. Defaults to 'down'. - example: down - required: - - userId - - name - - start - - multiplier - description: Array of boosts to create. Maximum 1,000 boosts per request. - maxItems: 1000 - minItems: 1 - required: - - systemKey - - boosts - CreatedPointsBoost: - title: CreatedPointsBoost - type: object - description: A successfully created points boost returned from the create endpoint. - properties: - id: - type: string - format: uuid - description: The UUID of the created boost. - name: - type: string - description: The name of the boost. - status: - type: string - enum: ['active', 'scheduled', 'finished'] - description: The status of the boost. - start: - type: string - format: date - description: The start date (YYYY-MM-DD). - end: - type: - - string - - 'null' - format: date - description: The end date (YYYY-MM-DD) or null if no end date. - multiplier: - type: number - description: The points multiplier. - rounding: - type: string - enum: - - down - - up - - nearest - description: How boosted points are rounded. - userId: - type: string - description: The customer ID of the user the boost was created for. - required: - - id - - name - - status - - start - - end - - multiplier - - rounding - - userId - CreatePointsBoostsResponse: - title: CreatePointsBoostsResponse - type: object - description: Response containing created boosts and any issues encountered while creating points boosts. - properties: - created: - type: array - items: - $ref: '#/components/schemas/CreatedPointsBoost' - description: Array of successfully created boosts. - issues: - type: array - items: - $ref: '#/components/schemas/BulkInsertIssue' - description: Array of issues encountered during boost creation. - required: - - created - - issues - ArchivePointsBoostsResponse: - title: ArchivePointsBoostsResponse - type: object - description: Response containing the count of archived points boosts. - properties: - archivedCount: - type: integer - description: The number of boosts that were archived. - example: 3 - required: - - archivedCount - RestoreStreaksRequest: - title: RestoreStreaksRequest - type: object - description: Request body for restoring streaks for multiple users. - properties: - users: - type: array - items: - type: object - properties: - id: - type: string - description: The ID of the user to restore streaks for. - example: user-123 - required: - - id - description: Array of users to restore streaks for. Maximum 100 users per request. - maxItems: 100 - minItems: 1 - example: - - id: user-123 - - id: user-456 - required: - - users - RestoreStreaksResponse: - title: RestoreStreaksResponse - type: object - description: Response containing restored users and any issues encountered. - properties: - restoredUsers: - type: array - items: - type: string - description: Array of user IDs whose streaks were successfully restored. - example: - - user-123 - - user-456 - issues: - type: array - items: - $ref: '#/components/schemas/BulkInsertIssue' - description: Array of issues encountered during streak restoration. - required: - - restoredUsers - - issues - BulkInsertIssue: - title: BulkInsertIssue - type: object - description: An issue encountered while bulk inserting data. - properties: - userId: - type: string - description: The ID of the user the issue relates to. - example: user-123 - level: - type: string - enum: - - error - - warning - description: The severity level of the issue. - example: warning - reason: - type: string - description: A human-readable description of the issue. - example: Would exceed maximum freeze limit - required: - - userId - - level - - reason - WrappedMetric: - title: WrappedMetric - type: object - description: A user's metric data for a wrapped period. - properties: - name: - type: string - description: The name of the metric. - example: Words Written - units: - type: - - string - - 'null' - description: The units of the metric. - example: words - currentTotal: - type: number - description: The user's current total for the metric. - example: 15000 - changeThisPeriod: - type: number - description: The change in the metric value during the period. - example: 2500 - percentChange: - type: number - description: The percentage change in the metric value during the period. - example: 20 - percentileThisPeriod: - type: number - description: The user's percentile rank for this metric during the period. Only included for weekly, monthly, and yearly aggregation periods. - example: 85 - byAttribute: - type: object - additionalProperties: - type: object - additionalProperties: - type: object - properties: - name: - type: string - description: The name of the metric. - units: - type: - - string - - 'null' - description: The units of the metric. - currentTotal: - type: number - description: The current total for this attribute value. - changeThisPeriod: - type: number - description: The change during the period for this attribute value. - percentChange: - type: number - description: The percentage change for this attribute value. - percentileThisPeriod: - type: number - description: The user's percentile rank for this attribute value during the period. - description: Metric data broken down by attribute key and value. - required: - - name - - currentTotal - - changeThisPeriod - - percentChange - - byAttribute - WrappedPoints: - title: WrappedPoints - type: object - description: A user's points data for a wrapped period. - properties: - name: - type: string - description: The name of the points system. - example: Experience Points - description: - type: - - string - - 'null' - description: The description of the points system. - example: Points earned through activity - currentTotal: - type: number - description: The user's current total points. - example: 5000 - changeThisPeriod: - type: number - description: The change in points during the period. - example: 500 - percentChange: - type: number - description: The percentage change in points during the period. - example: 11.1 - percentileThisPeriod: - type: number - description: The user's percentile rank for this points system during the period. Only included for weekly, monthly, and yearly aggregation periods. - example: 88 - required: - - name - - currentTotal - - changeThisPeriod - - percentChange - WrappedStreak: - title: WrappedStreak - type: object - description: The user's longest streak during the wrapped period. - properties: - length: - type: integer - description: The length of the streak. - example: 45 - frequency: - $ref: '#/components/schemas/StreakFrequency' - description: The frequency of the streak. - periodStart: - type: - - string - - 'null' - format: date - description: The start date of the streak period. - example: '2024-02-01' - periodEnd: - type: - - string - - 'null' - format: date - description: The end date of the streak period. - example: '2024-03-17' - started: - type: - - string - - 'null' - format: date - description: The date the streak started. - example: '2024-02-01' - required: - - length - - frequency - - periodStart - - periodEnd - - started - WrappedActivityPeriod: - title: WrappedActivityPeriod - type: object - description: Activity data for a specific period (day, week, month, or year). - properties: - metrics: - type: object - additionalProperties: - $ref: '#/components/schemas/WrappedMetric' - description: The user's metrics during this period, keyed by metric key. - points: - type: object - additionalProperties: - $ref: '#/components/schemas/WrappedPoints' - description: The user's points during this period, keyed by points system key. - achievements: - type: array - items: - $ref: '#/components/schemas/UserAchievementResponse' - description: Achievements completed during this period. - leaderboards: - type: object - additionalProperties: - $ref: '#/components/schemas/UserLeaderboardResponse' - description: The user's best leaderboard rankings during this period, keyed by leaderboard key. - required: - - metrics - - points - - achievements - - leaderboards - WrappedMostActiveDay: - title: WrappedMostActiveDay - type: object - description: The user's most active day during the year. - allOf: - - $ref: '#/components/schemas/WrappedActivityPeriod' - - type: object - properties: - date: - type: string - format: date - description: The date of the most active day in YYYY-MM-DD format. - example: '2024-03-15' - required: - - date - WrappedMostActiveWeek: - title: WrappedMostActiveWeek - type: object - description: The user's most active week during the year. - allOf: - - $ref: '#/components/schemas/WrappedActivityPeriod' - - type: object - properties: - start: - type: string - format: date - description: The start date of the most active week in YYYY-MM-DD format. - example: '2024-03-11' - end: - type: string - format: date - description: The end date of the most active week in YYYY-MM-DD format. - example: '2024-03-17' - required: - - start - - end - WrappedMostActiveMonth: - title: WrappedMostActiveMonth - type: object - description: The user's most active month during the year. - allOf: - - $ref: '#/components/schemas/WrappedActivityPeriod' - - type: object - properties: - month: - type: integer - minimum: 0 - maximum: 11 - description: The month number (0-11, where 0 is January). - example: 2 - required: - - month - WrappedEntireYear: - title: WrappedEntireYear - type: object - description: The user's activity data for the entire year. - allOf: - - $ref: '#/components/schemas/WrappedActivityPeriod' - - type: object - properties: - longestStreak: - $ref: '#/components/schemas/WrappedStreak' - description: The user's longest streak during the year. - required: - - longestStreak - WrappedActivity: - title: WrappedActivity - type: object - description: The user's activity summary for the wrapped year. - properties: - daysActive: - type: integer - description: The number of days the user was active during the year. - example: 156 - weeksActive: - type: integer - description: The number of weeks the user was active during the year. - example: 42 - monthsActive: - type: integer - description: The number of months the user was active during the year. - example: 11 - mostActiveDay: - $ref: '#/components/schemas/WrappedMostActiveDay' - description: Data about the user's most active day. - mostActiveWeek: - $ref: '#/components/schemas/WrappedMostActiveWeek' - description: Data about the user's most active week. - mostActiveMonth: - $ref: '#/components/schemas/WrappedMostActiveMonth' - description: Data about the user's most active month. - entireYear: - $ref: '#/components/schemas/WrappedEntireYear' - description: Data about the user's activity for the entire year. - required: - - daysActive - - weeksActive - - monthsActive - - mostActiveDay - - mostActiveWeek - - mostActiveMonth - - entireYear - WrappedResponse: - title: WrappedResponse - type: object - description: A user's year-in-review wrapped data including activity summaries, metrics, points, achievements, streaks, and leaderboard rankings. - properties: - user: - $ref: '#/components/schemas/User' - description: The user's profile information. - activity: - $ref: '#/components/schemas/WrappedActivity' - description: The user's activity data for the wrapped year. - required: - - user - - activity - securitySchemes: - ApiKeyAuth: - type: apiKey - in: header - name: X-API-KEY -servers: - - x-fern-server-name: Application API - url: https://api.trophy.so/v1 - description: Application API - - x-fern-server-name: Admin API - url: https://admin.trophy.so/v1 - description: Admin API From f896a4f19fa0343abd4519481f34fd5e6c44c5eb Mon Sep 17 00:00:00 2001 From: Charlie Hopkins-Brinicombe Date: Wed, 15 Apr 2026 16:05:55 +0100 Subject: [PATCH 2/7] Update docs to reference composite attributes update --- .cursor/rules/localization-workflow.mdc | 1 + openapi.yml | 113 ++++++++++++++++-- platform/achievements.mdx | 8 +- platform/leaderboards.mdx | 15 ++- platform/points.mdx | 7 +- ...d-rankings-request-multiple-attributes.mdx | 16 +-- snippets/points-system-response-block.mdx | 5 +- 7 files changed, 141 insertions(+), 24 deletions(-) diff --git a/.cursor/rules/localization-workflow.mdc b/.cursor/rules/localization-workflow.mdc index 08be1c0..85b9a3a 100644 --- a/.cursor/rules/localization-workflow.mdc +++ b/.cursor/rules/localization-workflow.mdc @@ -6,6 +6,7 @@ alwaysApply: true # Localization Workflow - Treat repo-root docs paths as the canonical English source and keep identical relative file paths/filenames across all locales. +- For planned doc changes, default scope to English source files only. Do not plan or execute manual edits to non-English locale content (for example `es/**`) unless the user explicitly asks for localized updates; rely on the existing translation workflow to propagate English changes. - Keep all locale navigation in `docs.json` under `navigation.languages`; do not reintroduce `navigation.global` or top-level `navbar`. - Keep SEO metatags in English unless explicitly requested otherwise. - Do not translate code snippets, inline code, URLs, route slugs, or API identifiers. diff --git a/openapi.yml b/openapi.yml index f244f0b..0887c31 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: Trophy - version: '1.2.0' + version: '1.3.3' paths: # APPLICATION API ------------------------------------------------------- @@ -78,6 +78,11 @@ paths: eventAttribute: key: source value: mobile-app + eventAttributes: + - key: source + value: mobile-app + - key: plan + value: premium - id: 5100fe51-6bce-6j44-b0hs-bddc4e123684 trigger: streak name: 10 days of exercise @@ -2573,6 +2578,11 @@ paths: eventAttribute: key: source value: mobile-app + eventAttributes: + - key: source + value: mobile-app + - key: plan + value: premium created: '2021-01-01T00:00:00Z' updated: '2021-01-01T00:00:00Z' - id: 0040fe51-6bce-4b44-b0ad-bddc4e123536 @@ -2930,6 +2940,9 @@ paths: end: null maxParticipants: 100 breakdownAttribute: null + breakdownAttributes: + - country + - plan runUnit: day runInterval: 7 - id: 5100fe51-6bce-6j44-b0hs-bddc4e123683 @@ -2944,6 +2957,7 @@ paths: end: null maxParticipants: 50 breakdownAttribute: null + breakdownAttributes: [] runUnit: null runInterval: null '401': @@ -4559,7 +4573,8 @@ components: type: - integer - 'null' - description: The user's rank across all users. Null if the user has no active streak. + description: Deprecated. The user's rank across all users. Null if the user has no active streak. + deprecated: true example: 5 required: - rank @@ -4596,6 +4611,23 @@ components: timeInterval: type: integer description: If the trigger has type 'time', the numer of units of timeUnit after which to award points + eventAttributes: + type: array + description: If the trigger has type 'metric', the event attributes that must match for the trigger to award points. Empty when the trigger is metric-based and has no event attribute filters. Omitted for non-metric triggers. + items: + type: object + properties: + key: + type: string + description: The key of the event attribute. + example: source + value: + type: string + description: The required value of the event attribute. + example: mobile-app + required: + - key + - value PointsAward: title: PointsAward type: object @@ -4847,8 +4879,16 @@ components: type: - string - 'null' - description: The key of the attribute to break down this leaderboard by. + description: Deprecated. The key of the attribute to break down this leaderboard by. + deprecated: true example: country + breakdownAttributes: + type: array + description: The user attribute keys that this leaderboard is broken down by. + items: + type: string + description: The key of a user attribute in this leaderboard breakdown. + example: country metricKey: type: string description: The key of the metric to rank by, if rankBy is 'metric'. @@ -4908,6 +4948,7 @@ components: - description - rankBy - breakdownAttribute + - breakdownAttributes - start - end - maxParticipants @@ -4999,8 +5040,26 @@ components: example: 25000 breakdownAttributeValue: type: string - description: For leaderboards with a breakdown attribute, the value of the attribute for the user. + description: Deprecated. For leaderboards with a single breakdown attribute, the value of that attribute for the user. + deprecated: true example: USA + breakdownAttributeValues: + type: array + description: For leaderboards with breakdown attributes, the user's values for each breakdown attribute. + items: + type: object + properties: + key: + type: string + description: The key of the breakdown attribute. + example: country + value: + type: string + description: The user's value for the breakdown attribute. + example: USA + required: + - key + - value required: - start - end @@ -5057,7 +5116,7 @@ components: description: The name of the metric associated with this achievement (only applicable if trigger = 'metric') userAttributes: type: array - description: User attribute filters that must be met for this achievement to be completed. Only present if the achievement has user attribute filters configured. + description: User attribute filters that must be met for this achievement to be completed. items: type: object properties: @@ -5074,7 +5133,8 @@ components: - value eventAttribute: type: object - description: Event attribute filter that must be met for this achievement to be completed. Only present if the achievement has an event filter configured. + description: Deprecated. Event attribute filter that must be met for this achievement to be completed. Only present if the achievement has an event filter configured. + deprecated: true properties: key: type: string @@ -5087,12 +5147,30 @@ components: required: - key - value + eventAttributes: + type: array + description: Event attribute filters that must be met for this achievement to be completed. Omitted for non-metric achievements. + items: + type: object + properties: + key: + type: string + description: The key of the event attribute. + example: source + value: + type: string + description: The value of the event attribute. + example: mobile-app + required: + - key + - value required: - id - name - trigger - description - badgeUrl + - userAttributes UserAchievementResponse: title: UserAchievementResponse type: object @@ -5514,7 +5592,7 @@ components: description: The interval of the trigger in the time unit, if the trigger is a time interval. userAttributes: type: array - description: User attribute filters that must be met for this trigger to activate. Only present if the trigger has user attribute filters configured. + description: User attribute filters that must be met for this trigger to activate. items: type: object properties: @@ -5531,7 +5609,8 @@ components: - value eventAttribute: type: object - description: Event attribute filter that must be met for this trigger to activate. Only present if the trigger has an event filter configured. + description: Deprecated. Event attribute filter that must be met for this trigger to activate. Only present if the trigger has an event filter configured. + deprecated: true properties: key: type: string @@ -5544,6 +5623,23 @@ components: required: - key - value + eventAttributes: + type: array + description: Event attribute filters that must be met for this trigger to activate. Present when the trigger uses one or more event attribute filters. + items: + type: object + properties: + key: + type: string + description: The key of the event attribute. + example: source + value: + type: string + description: The required value of the event attribute. + example: mobile-app + required: + - key + - value created: type: string format: date-time @@ -5557,6 +5653,7 @@ components: - type - points - status + - userAttributes - created - updated PointsSystemResponse: diff --git a/platform/achievements.mdx b/platform/achievements.mdx index fcf2ebc..97b22d5 100644 --- a/platform/achievements.mdx +++ b/platform/achievements.mdx @@ -158,10 +158,16 @@ To create new achievements, head to the [achievements page](https://app.trophy.s You can assign attribute filters to an achievement to further restrict who can unlock them and when. -- To limit a **Metric** achievement to only apply to events with specific [custom event attributes](/platform/events#custom-event-attributes), select an attribute and enter a value in the **Event Attribute** section. +- To limit a **Metric** achievement to only apply to events with specific [custom event attributes](/platform/events#custom-event-attributes), add one or more filters in the **Event Attributes** section. - To limit any type of achievement to only apply to a user with one or more specific [custom user attributes](/platform/users#custom-user-attributes), add attributes and the desired values in the **User Attributes** section. + + When you set multiple event attribute filters, all of them must match for the + metric achievement to apply. In API responses, `eventAttributes` is the + canonical field and `eventAttribute` is deprecated for legacy compatibility. + + diff --git a/platform/leaderboards.mdx b/platform/leaderboards.mdx index 80f18cc..f576c04 100644 --- a/platform/leaderboards.mdx +++ b/platform/leaderboards.mdx @@ -99,16 +99,16 @@ Streak leaderboards rank users based on their current streak length. If you have a large user base, it's best practice to split up leaderboard participants into smaller, more socially-connected groups. This often leads to higher engagement than when using global leaderboards. -Leaderboards in Trophy can be configured to group users into smaller groups according to a specific [custom user attribute](/platform/users#custom-user-attributes). +Leaderboards in Trophy can be configured to group users into smaller groups according to one or more [custom user attributes](/platform/users#custom-user-attributes). When using leaderboard breakdowns, [participant limits](#participant-limits) apply at the group level, not overall. -To set up a leaderboard breakdown head to the leaderboard configuration page and create or select your user attribute in the 'Breakdown Attribute' field. +To set up a leaderboard breakdown, head to the leaderboard configuration page and create or select one or more user attributes in the 'Breakdown Attributes' field. -Trophy will automatically start grouping users into smaller leaderboards based on the values of your chosen attribute for each of your users. +Trophy will automatically start grouping users into smaller leaderboards based on the values of your chosen attributes for each user. -To fetch rankings for a particular group of users with a specific attribute value, use the [leaderboard rankings API](/api-reference/endpoints/leaderboards/get-leaderboard), specifying the attribute value in the `userAttributes` parameter as follows: +To fetch rankings for a particular group of users with a specific attribute value, use the [leaderboard rankings API](/api-reference/endpoints/leaderboards/get-leaderboard), specifying one attribute filter in the `userAttributes` parameter as follows: -If you wish to fetch rankings for a particular group of users with a specific combination of user attributes, create a new attribute to track the combination and use that as your breakdown attribute as follows: +To fetch rankings for a particular group of users with a specific combination of user attributes, pass multiple comma-delimited `key:value` filters in `userAttributes` as follows: + + When multiple `userAttributes` filters are provided, all filters must match + for a user to be included in the returned rankings. + + ## Start & End Dates {#start-end-dates} Use start and end dates to control the window within which leaderboards are actively ranking users. diff --git a/platform/points.mdx b/platform/points.mdx index e834b43..2b48aa9 100644 --- a/platform/points.mdx +++ b/platform/points.mdx @@ -152,10 +152,15 @@ To create a new points trigger, head to the points system that you want to creat You can assign attribute filters to a points trigger to further restrict when they apply. -- To limit a **Metric trigger** to only apply to events with specific [custom event attributes](/platform/events#custom-event-attributes), select an attribute and enter a value in the **Event Attribute** section. +- To limit a **Metric trigger** to only apply to events with specific [custom event attributes](/platform/events#custom-event-attributes), add one or more filters in the **Event Attributes** section. - To limit any type of trigger to only apply to a user with one or more specific [custom user attributes](/platform/users#custom-user-attributes), add attributes and the desired values in the **User Attributes** section. + + When you set multiple event attribute filters, all of them must match for the + metric trigger to fire. + + diff --git a/snippets/leaderboard-rankings-request-multiple-attributes.mdx b/snippets/leaderboard-rankings-request-multiple-attributes.mdx index dbb98d8..4ab3b59 100644 --- a/snippets/leaderboard-rankings-request-multiple-attributes.mdx +++ b/snippets/leaderboard-rankings-request-multiple-attributes.mdx @@ -1,7 +1,7 @@ ```bash cURL curl --request GET \ - --url https://api.trophy.so/v1/leaderboards/{key}?userAttributes=region_city:southeast_london \ + --url https://api.trophy.so/v1/leaderboards/{key}?userAttributes=region:southeast,city:london \ --header 'X-API-KEY: ' ``` @@ -10,7 +10,7 @@ trophy.leaderboards.get("daily_champions", { offset: 0, limit: 10, run: "2025-01-15", - userAttributes: "region_city:southeast_london" + userAttributes: "region:southeast,city:london" }); ``` @@ -20,7 +20,7 @@ client.leaderboards.get( offset=0, limit=10, run="2025-01-15", - user_attributes="region_city:southeast_london" + user_attributes="region:southeast,city:london" ) ``` @@ -29,7 +29,7 @@ $request = new LeaderboardsGetRequest([ 'offset' => 0, 'limit' => 10, 'run' => "2025-01-15", - 'user_attributes' => "region_city:southeast_london" + 'user_attributes' => "region:southeast,city:london" ]); $trophy->leaderboards->get("daily_champions", $request); @@ -40,7 +40,7 @@ LeaderboardsGetRequest request = LeaderboardsGetRequest.builder() .offset(0) .limit(10) .run("2025-01-15") - .userAttributes("region_city:southeast_london") + .userAttributes("region:southeast,city:london") .build(); LeaderboardsGetResponse response = client.leaderboards().get("daily_champions", request); @@ -53,7 +53,7 @@ response, err := client.Leaderboards.Get( Offset: 0, Limit: 10, Run: "2025-01-15", - UserAttributes: "region_city:southeast_london", + UserAttributes: "region:southeast,city:london", }, ) ``` @@ -63,7 +63,7 @@ var request = new LeaderboardsGetRequest { Offset = 0, Limit = 10, Run = "2025-01-15", - UserAttributes = "region_city:southeast_london" + UserAttributes = "region:southeast,city:london" }; await trophy.Leaderboards.GetAsync("daily_champions", request); @@ -75,7 +75,7 @@ result = client.Leaderboards.Get( :offset => 0, :limit => 10, :run => "2025-01-15", - :user_attributes => "region_city:southeast_london" + :user_attributes => "region:southeast,city:london" ) ``` diff --git a/snippets/points-system-response-block.mdx b/snippets/points-system-response-block.mdx index 90eb952..77e1068 100644 --- a/snippets/points-system-response-block.mdx +++ b/snippets/points-system-response-block.mdx @@ -20,7 +20,10 @@ { "key": "plan-type", "value": "premium" }, { "key": "region", "value": "us-east" } ], - "eventAttribute": { "key": "source", "value": "mobile-app" }, + "eventAttributes": [ + { "key": "source", "value": "mobile-app" }, + { "key": "platform", "value": "ios" } + ], "created": "2021-01-01T00:00:00Z", "updated": "2021-01-01T00:00:00Z" }, From fe0e417013e460e7faa340d573544ddbada37c48 Mon Sep 17 00:00:00 2001 From: Charlie Hopkins-Brinicombe Date: Wed, 15 Apr 2026 16:10:53 +0100 Subject: [PATCH 3/7] Update lock file --- i18n.lock | 489 +++++++++++++++++++++++++++--------------------------- 1 file changed, 246 insertions(+), 243 deletions(-) diff --git a/i18n.lock b/i18n.lock index 5eeebe0..6197593 100644 --- a/i18n.lock +++ b/i18n.lock @@ -1229,74 +1229,75 @@ checksums: content/47: 6e12b58d5a6d92e53d56ecb9168bacc3 content/48: 640d0b31f6faa54914d25c81f5dbf413 content/49: a3b1e396e4d757d9f716937605f6c513 - content/50: f8427fca0863acab3093d7aa878fbfeb + content/50: 64ed1b5df7db0c1c23a109495bd2c951 content/51: 990ceededf6800238405016bcd11d737 - content/52: 640d0b31f6faa54914d25c81f5dbf413 - content/53: 07658aa358af3ef9d173c3f75751a176 - content/54: 88894d9a6f1d49cb0329825bb76e5a17 - content/55: 25b662df6a87d36e4925a6af4247888d - content/56: 374b1eb956e935cf3f51c46af62831ac - content/57: ad94b06eaa66c2fd263362b6af04b9c0 - content/58: c6906582420cbe603d1bb135d7cdc13a - content/59: e6d0829ea90222582df021491c834609 - content/60: c6b7171acaf9ea0be21b408e4beb6c73 - content/61: bbfc8b8ec96997b3e589802f18424196 - content/62: a57adc8d19410c1974f8d59a2e2e1cb9 - content/63: f83c5cc385540bbff0ce0280e31d703d - content/64: 8040541596306a73329cc1f4c375d616 - content/65: c32975398faa1ee29ba3cdfd5bd5e314 - content/66: 0a33871a9ed20bf823cb2c45dc0c91d1 - content/67: 8bff38b0eba94b9ea1b67e7472b74cfc - content/68: 3194ad8ddf9bbe2500b571a9417e2101 - content/69: bec0c223723733b84c83e6d13b637855 - content/70: 1c526016dbe08ff63980dc829cc327f7 - content/71: ce50bb578174216d8d0625907a242263 - content/72: 61f4238f95cc209302563fe0d66aea20 - content/73: 3cc855439498819a71e24a9d44678705 - content/74: dd256e45824b048778e81a0e80a3a6cf - content/75: b384ef84f794014c78bd7232f18d3b5f - content/76: 19f49f0312ddc4ded8f6737083673edf - content/77: 9572e0cb99536535c176db24863ce217 - content/78: 40a3888d076524edcb9716f64683c5b3 - content/79: 041645f33a12c870680e713dc7b8d78a - content/80: 98200505f691d3dcb12fa6833d0e88ad - content/81: 6fecb4eaca40bae0bb37fbe65ff6a637 - content/82: 4455b21dd9cea4b18d0d3991c010eacb - content/83: 61b6140fd37b3d64ac6cf6a5529119a8 - content/84: 539769066341e46b743c0cfda822f3f9 - content/85: c66f09fe4fa88a9e016ebe4f0a2ed9b0 - content/86: 9461907d8be2aa04f16b3d6cae972142 - content/87: 7f85a8c7e5217b02244281defb8e8592 - content/88: 46f1082308b9860120a46a27c8b14892 - content/89: 43e6ec093695c83476ddafd3674f8ec0 - content/90: 5425ae9feefbe5a5ce28290afc321625 - content/91: 853f6a32a56f50b2366cc2e9bfe30cef - content/92: 54dcc7d2d5e4c5ed2c9303950e89ab0d - content/93: aa1e5bc107219b8f73778df6a2eaf649 - content/94: 3898b08aad245d481c00759bba165827 - content/95: 5a26c709955575918c0b6c9093f47410 - content/96: cbb6dd927a2ba8dbe47777babba90548 - content/97: 080834714f41b28fa5e15cc6139d4346 - content/98: 2c595c5d764fd8e4bc386193ea202888 - content/99: 083e769853a4074246e3b140c55e2778 - content/100: a24eeda297d1c1e30583069309251316 - content/101: d860fb4c61d9774d524a4136c86cb598 - content/102: cc20ee2981f70eeb7e0fa675624ef43d - content/103: 29c9df71eb3d97cd840e59bfa6d65acc - content/104: 50c9b9a3be9b72db4c1dbc46fb96e835 - content/105: 8f85d64b4bb8cedd8d9ee8cc962ec075 - content/106: 79524f874744893afda00224a0203ea6 - content/107: 656598f8d7330171f0a3f1f73e884e9f - content/108: eb7a29fcbea23aeecc0192229112e275 - content/109: 25d2f401cdd79bd9c1e761535eb145ce - content/110: 1c813fba4719c7b29863b4944f3dc074 - content/111: 450ad7f99b79b12599856f813b9ef758 - content/112: 39f313714117fbf6ed7b64076ffe1498 - content/113: bb4a3e2c68e0f832f815b659210ef260 - content/114: aae63d8bf6b6da4ac6fb501e13691e4d - content/115: 7a451aee1061c1cf2a4108f13acbafff - content/116: 10e8e9a59847ddd720c6d06b12c7b120 - content/117: 9799b973234bf2d10d5c971b267f55a5 + content/52: 42facdcb08b410e8073ea13de4db8541 + content/53: 640d0b31f6faa54914d25c81f5dbf413 + content/54: 07658aa358af3ef9d173c3f75751a176 + content/55: 88894d9a6f1d49cb0329825bb76e5a17 + content/56: 25b662df6a87d36e4925a6af4247888d + content/57: 374b1eb956e935cf3f51c46af62831ac + content/58: ad94b06eaa66c2fd263362b6af04b9c0 + content/59: c6906582420cbe603d1bb135d7cdc13a + content/60: e6d0829ea90222582df021491c834609 + content/61: c6b7171acaf9ea0be21b408e4beb6c73 + content/62: bbfc8b8ec96997b3e589802f18424196 + content/63: a57adc8d19410c1974f8d59a2e2e1cb9 + content/64: f83c5cc385540bbff0ce0280e31d703d + content/65: 8040541596306a73329cc1f4c375d616 + content/66: c32975398faa1ee29ba3cdfd5bd5e314 + content/67: 0a33871a9ed20bf823cb2c45dc0c91d1 + content/68: 8bff38b0eba94b9ea1b67e7472b74cfc + content/69: 3194ad8ddf9bbe2500b571a9417e2101 + content/70: bec0c223723733b84c83e6d13b637855 + content/71: 1c526016dbe08ff63980dc829cc327f7 + content/72: ce50bb578174216d8d0625907a242263 + content/73: 61f4238f95cc209302563fe0d66aea20 + content/74: 3cc855439498819a71e24a9d44678705 + content/75: dd256e45824b048778e81a0e80a3a6cf + content/76: b384ef84f794014c78bd7232f18d3b5f + content/77: 19f49f0312ddc4ded8f6737083673edf + content/78: 9572e0cb99536535c176db24863ce217 + content/79: 40a3888d076524edcb9716f64683c5b3 + content/80: 041645f33a12c870680e713dc7b8d78a + content/81: 98200505f691d3dcb12fa6833d0e88ad + content/82: 6fecb4eaca40bae0bb37fbe65ff6a637 + content/83: 4455b21dd9cea4b18d0d3991c010eacb + content/84: 61b6140fd37b3d64ac6cf6a5529119a8 + content/85: 539769066341e46b743c0cfda822f3f9 + content/86: c66f09fe4fa88a9e016ebe4f0a2ed9b0 + content/87: 9461907d8be2aa04f16b3d6cae972142 + content/88: 7f85a8c7e5217b02244281defb8e8592 + content/89: 46f1082308b9860120a46a27c8b14892 + content/90: 43e6ec093695c83476ddafd3674f8ec0 + content/91: 5425ae9feefbe5a5ce28290afc321625 + content/92: 853f6a32a56f50b2366cc2e9bfe30cef + content/93: 54dcc7d2d5e4c5ed2c9303950e89ab0d + content/94: aa1e5bc107219b8f73778df6a2eaf649 + content/95: 3898b08aad245d481c00759bba165827 + content/96: 5a26c709955575918c0b6c9093f47410 + content/97: cbb6dd927a2ba8dbe47777babba90548 + content/98: 080834714f41b28fa5e15cc6139d4346 + content/99: 2c595c5d764fd8e4bc386193ea202888 + content/100: 083e769853a4074246e3b140c55e2778 + content/101: a24eeda297d1c1e30583069309251316 + content/102: d860fb4c61d9774d524a4136c86cb598 + content/103: cc20ee2981f70eeb7e0fa675624ef43d + content/104: 29c9df71eb3d97cd840e59bfa6d65acc + content/105: 50c9b9a3be9b72db4c1dbc46fb96e835 + content/106: 8f85d64b4bb8cedd8d9ee8cc962ec075 + content/107: 79524f874744893afda00224a0203ea6 + content/108: 656598f8d7330171f0a3f1f73e884e9f + content/109: eb7a29fcbea23aeecc0192229112e275 + content/110: 25d2f401cdd79bd9c1e761535eb145ce + content/111: 1c813fba4719c7b29863b4944f3dc074 + content/112: 450ad7f99b79b12599856f813b9ef758 + content/113: 39f313714117fbf6ed7b64076ffe1498 + content/114: bb4a3e2c68e0f832f815b659210ef260 + content/115: aae63d8bf6b6da4ac6fb501e13691e4d + content/116: 7a451aee1061c1cf2a4108f13acbafff + content/117: 10e8e9a59847ddd720c6d06b12c7b120 + content/118: 9799b973234bf2d10d5c971b267f55a5 6a9739e5e2a2b2be16e4da3ab6f64867: meta/title: 230344f713c89383f51389be1e0d3f0d meta/description: 0a913183ce5ee8cfafa21bafe1b0a3e1 @@ -1620,78 +1621,79 @@ checksums: content/33: 6ee187c17478546ee6995ddc7ff71f7d content/34: fd28f573fd8a586b5367a4f9b28e3769 content/35: 5f8179f48cce17a9dfc61a6e5e25635c - content/36: b1e0b4a77be6bf4155907e2535c08ce8 + content/36: 44e62155a0426ebb1e2cfe3452859c1c content/37: 64e7d4b48377ae88bdb94c0526f3ac52 - content/38: 8585c7df50d3e4a85a4fdac34176f248 - content/39: 87ffdb6e1e2f3aa7f0988a50eed855ec + content/38: 00d6230eeeda9c646035bb68b08682f6 + content/39: 1389ca754bbb08581696ce740b2f7afa content/40: 5c144d24f704e1a240b89ab6df4148ba - content/41: 5715f85e92401fbe93574ae58e5cd5b7 + content/41: 5e38c4efb5d411c77e4b36260df89ce0 content/42: d67621c5d4d9fd33358a7ecec0338342 - content/43: 18b88784316174b560051890fe13efa0 + content/43: 218e40ee0c9c118800a6bbc8c22a0406 content/44: 73dc7193c431ee27face3e96cca59471 - content/45: 2da3bce39f011d227e7439bf39000c4b - content/46: 376db24492fd179249a97399cac62c7b - content/47: cbe733e8e67272ce1e1719f34c29ce16 - content/48: be8ee42c61c2f8412a2223346fd1e5d7 - content/49: 19ddbf4a6a27d1217a35ff1ab7082883 - content/50: 6978c15affabcb936aeac6d02d2a9201 - content/51: f53d2373b481a20fe76d332f4a102b84 - content/52: 26f14651033af5a7e0da93c5dd653916 - content/53: 84be083ce8f618a49aa37c1b5ebae0ad - content/54: 29eba1f6a44ec24ee2b0d97faff09564 - content/55: 7fc3a5a4f12e019d5546b6c8688751ce - content/56: 7034f8f1bcdb020342cc70f6ace334b7 - content/57: 610db0f043625ec6ace32150e8b96a66 - content/58: 094e89e6ac397f5e15ea0fc1c209558d - content/59: 20e3ac38d3cc61e817f1dffacaa1c21e - content/60: defd8dab42b430e9687cc7ca90e0052e - content/61: 4dec52ce04aa8849a8a60508baae30ae - content/62: 258c9459be6a0fc3f535b41a89922f50 - content/63: 830481b98e88ae69a3971df3aff5d250 - content/64: 538df8a5de4562fa88ae14bd586adc97 - content/65: f90fc6fdfdcb80d45d9e53b6591d7331 - content/66: b0655e68a1c5d9579fbcfdd75db04fce - content/67: 71d221bcbeb83b663ee8c0a0e4975b04 - content/68: a40d8cc248a02d4ce34ae16119b14bf8 - content/69: 4e5ddca85ed818337a2757ae6bc2dc64 - content/70: a27e8a0a72073744f925f2820a1b7684 - content/71: 73aea3ef20f5f9d1626fcc73fdea597d - content/72: a01a451f7aba1a80f0af7148df811dfc - content/73: 84a204a7da04ff738ebcc5f4d3a5ea35 - content/74: 3347863434fc4a0bd8f075b971428c95 - content/75: bb2ac20c712241859473f415c26a0a11 - content/76: e180f5d5909fef8296047e0782fe8a85 - content/77: 6cc6ea0c653430147746ed145777d8e2 - content/78: 214aa9e0d35236656ba1738c8be4c3f0 - content/79: 919b58f12a4f16d63b5a35d18fdf04bb - content/80: 2ca28a1219570c10ddeaf4cb1157bb6d - content/81: a6390089f6e92430098d3d97c610c17a - content/82: 3710272f77f1cbde0159b940d8826829 - content/83: ff0a8fe413f503e4af629f445746a3dd - content/84: 300bcdb15fbcadfde3db772fafe63524 - content/85: 3d93f05e4ce92565d044836c948352a9 - content/86: 0d08ff106c532b64677561ea2c3bad1f - content/87: 116df75dc256a5019b4809510f35677c - content/88: d6f6ef2bbe6d6ec5e267b33ba910ca7d - content/89: 0ab559de8fc24b6e6cca6175444cfe9a - content/90: fab414b91a7a42cd5e37a0c5ade9945a - content/91: ed80be5f381f63c9293cc5c4182c0632 - content/92: 2d9538daa0f3bcd2d760598097ee5d80 - content/93: 9c3265eff155d4a823fd955cde7dba35 - content/94: ff3d86520f4003c526e008912dbd57fd - content/95: ed10345c824c6fc4c69c882e9983b12b - content/96: fb9ce2e2edb7815efec73b696b5d5e95 - content/97: 25836d79f15e851139e61b1d31fd3c73 - content/98: 44d8170f535788a47c7874e25dedb768 - content/99: fa9f6f1b8a7829a8e68a1d0340a84533 - content/100: 857c1365962ba1b8c02e75f877a5b586 - content/101: 79524f874744893afda00224a0203ea6 - content/102: 0fa45599d835da6f2e916859554673eb - content/103: 450ad7f99b79b12599856f813b9ef758 - content/104: 4f9f49c4756aa59d40fcc76ca15669a9 - content/105: f151f31b2581a7bd7f7d8635bff09dc8 - content/106: 10e8e9a59847ddd720c6d06b12c7b120 - content/107: 9799b973234bf2d10d5c971b267f55a5 + content/45: abdc26659c4e48ac2a87a447c8069e21 + content/46: 2da3bce39f011d227e7439bf39000c4b + content/47: 376db24492fd179249a97399cac62c7b + content/48: cbe733e8e67272ce1e1719f34c29ce16 + content/49: be8ee42c61c2f8412a2223346fd1e5d7 + content/50: 19ddbf4a6a27d1217a35ff1ab7082883 + content/51: 6978c15affabcb936aeac6d02d2a9201 + content/52: f53d2373b481a20fe76d332f4a102b84 + content/53: 26f14651033af5a7e0da93c5dd653916 + content/54: 84be083ce8f618a49aa37c1b5ebae0ad + content/55: 29eba1f6a44ec24ee2b0d97faff09564 + content/56: 7fc3a5a4f12e019d5546b6c8688751ce + content/57: 7034f8f1bcdb020342cc70f6ace334b7 + content/58: 610db0f043625ec6ace32150e8b96a66 + content/59: 094e89e6ac397f5e15ea0fc1c209558d + content/60: 20e3ac38d3cc61e817f1dffacaa1c21e + content/61: defd8dab42b430e9687cc7ca90e0052e + content/62: 4dec52ce04aa8849a8a60508baae30ae + content/63: 258c9459be6a0fc3f535b41a89922f50 + content/64: 830481b98e88ae69a3971df3aff5d250 + content/65: 538df8a5de4562fa88ae14bd586adc97 + content/66: f90fc6fdfdcb80d45d9e53b6591d7331 + content/67: b0655e68a1c5d9579fbcfdd75db04fce + content/68: 71d221bcbeb83b663ee8c0a0e4975b04 + content/69: a40d8cc248a02d4ce34ae16119b14bf8 + content/70: 4e5ddca85ed818337a2757ae6bc2dc64 + content/71: a27e8a0a72073744f925f2820a1b7684 + content/72: 73aea3ef20f5f9d1626fcc73fdea597d + content/73: a01a451f7aba1a80f0af7148df811dfc + content/74: 84a204a7da04ff738ebcc5f4d3a5ea35 + content/75: 3347863434fc4a0bd8f075b971428c95 + content/76: bb2ac20c712241859473f415c26a0a11 + content/77: e180f5d5909fef8296047e0782fe8a85 + content/78: 6cc6ea0c653430147746ed145777d8e2 + content/79: 214aa9e0d35236656ba1738c8be4c3f0 + content/80: 919b58f12a4f16d63b5a35d18fdf04bb + content/81: 2ca28a1219570c10ddeaf4cb1157bb6d + content/82: a6390089f6e92430098d3d97c610c17a + content/83: 3710272f77f1cbde0159b940d8826829 + content/84: ff0a8fe413f503e4af629f445746a3dd + content/85: 300bcdb15fbcadfde3db772fafe63524 + content/86: 3d93f05e4ce92565d044836c948352a9 + content/87: 0d08ff106c532b64677561ea2c3bad1f + content/88: 116df75dc256a5019b4809510f35677c + content/89: d6f6ef2bbe6d6ec5e267b33ba910ca7d + content/90: 0ab559de8fc24b6e6cca6175444cfe9a + content/91: fab414b91a7a42cd5e37a0c5ade9945a + content/92: ed80be5f381f63c9293cc5c4182c0632 + content/93: 2d9538daa0f3bcd2d760598097ee5d80 + content/94: 9c3265eff155d4a823fd955cde7dba35 + content/95: ff3d86520f4003c526e008912dbd57fd + content/96: ed10345c824c6fc4c69c882e9983b12b + content/97: fb9ce2e2edb7815efec73b696b5d5e95 + content/98: 25836d79f15e851139e61b1d31fd3c73 + content/99: 44d8170f535788a47c7874e25dedb768 + content/100: fa9f6f1b8a7829a8e68a1d0340a84533 + content/101: 857c1365962ba1b8c02e75f877a5b586 + content/102: 79524f874744893afda00224a0203ea6 + content/103: 0fa45599d835da6f2e916859554673eb + content/104: 450ad7f99b79b12599856f813b9ef758 + content/105: 4f9f49c4756aa59d40fcc76ca15669a9 + content/106: f151f31b2581a7bd7f7d8635bff09dc8 + content/107: 10e8e9a59847ddd720c6d06b12c7b120 + content/108: 9799b973234bf2d10d5c971b267f55a5 977811daa4a70d47883b868820f2faf6: meta/title: 76712cf76855f695712cf05b994caebd meta/description: 2ff5fa3d67801d9c8ac8e3b3c12d930f @@ -1791,115 +1793,116 @@ checksums: content/42: 640d0b31f6faa54914d25c81f5dbf413 content/43: e466f1ff116db35bc5803115bf80d07f content/44: 5b97682d8de32b1cdf287450ea6589ac - content/45: 021b2cc2690448c1c628ab330384609b + content/45: 19b3d1278e686840d42ac104c3540fdd content/46: a75a0e13bfec5fcaf46f0eab0c4f0c46 - content/47: 640d0b31f6faa54914d25c81f5dbf413 - content/48: 97fb14f09ea66ad1636fbe714fc54ca9 - content/49: aebfce781560272a78a1d6777b43636e - content/50: 0bed4847273bc1b883857c1a2dc58e89 - content/51: 58e77d0bc7402a709dbf54c4586208da - content/52: 7d445112e8e8c53a3bb78f3e0e53902d - content/53: 7b0c9986d8c79303926b5293cdb615c7 - content/54: 1421905fe50ea83bf892ed3a36ead4eb - content/55: 4eee624283334d8490037e07c14cc9f2 - content/56: c48e258e50122f0eb0bc8e89d5ad653e - content/57: aa2a6f4894a5d9b0b351702724d1b62b - content/58: 7e45f5de94c34c9dedf5f4636295e4c8 - content/59: a1d30d251f782065bf113889d28034d3 - content/60: 30444d8cb60acb7f30d3c7828e85cd4d - content/61: a03b9429133989d89ee9af6e111d13cc - content/62: 48a7ad3f0849fd9e3f29194c3033294b - content/63: 8ac07d18b175b18a7cc30397cdfc6235 - content/64: 725a7d24863328500e04505306d3f530 - content/65: 6ba587262d22a5b86d7a8ad89a9cd7e6 - content/66: 5d1b2cc1499b5dcd168c5cee8a0eb263 - content/67: 318e0c3aac0a4c2b6ea68d6586af76ed - content/68: 9f481780c40177897b369188ac4e14d1 - content/69: 5b1f5840c84fc15b238be30621c7040d - content/70: 36724b1e1a7d8ede3c3473511e0962c8 - content/71: 7dbb6d2a32807f07f8d15f295d6029c1 - content/72: 94ae1c950a664c7d6a71f543b3590645 - content/73: 959f4ff4a6314dcecc014d943a286a78 - content/74: e1bc33e13fc0ea4ccae263700fb87972 - content/75: e2a8aa98d08edf0385e18206dfcf209c - content/76: 0e4b2f9ac55cbfc1f8478c16fe462461 - content/77: 78a7ec2ac9e6297a1b73b52737d2f703 - content/78: 31209e62b8be12e97e731873a4b5f1dd - content/79: 65a222bfff97f1c2e088bbf2a6e64a98 - content/80: ce50ee3c3c46578e46a5b654992a7184 - content/81: 609fc14a8f76e947f5e0910252a84601 - content/82: 0762a64c782ab06b2497cb1e9bcd040c - content/83: ca93dd4e4bfb89506d0606d5504bb698 - content/84: 70ae6f507d495c0e7f08381093844545 - content/85: faef3168052f65fcae43f4e282c3405a - content/86: 37f44002ca6faa7edbb155f8ff13afe2 - content/87: af0d661c840762599d5bacf04887a063 - content/88: a8e033c5125c9b6035601165bf457168 - content/89: cf2fad1b631722b00fe74c4bc83c1c8f - content/90: da795a2fd9f1b2a97a753e5c7d7a31e0 - content/91: 2899bbcc52d19fd5403c595171e4428a - content/92: 34384a0b1a14658974468de46ae841b9 - content/93: 416104e9a22d7c8eff48d9aaf3a295ce - content/94: 2e210c407617dce99236163adcda8eee - content/95: 3225628004c6c872589b85bb8f01e922 - content/96: b984c1ec400c5b64605c8183fb0de89f - content/97: 0e97ce08c4c8aa23cd7bcb8c178f0526 - content/98: 69bbcf5ddbf3bc375a1c1dc9c0c543b1 - content/99: f4d0a6a649c7cdcdbf44cb505164c246 - content/100: f2b75ff3cb9bb1773411fd9a23b40bfe - content/101: e1b9c5e40f0f7590b6915570836f2ffc - content/102: 840d57f470753b5f90ee96f9664d4488 - content/103: fcf33f9e0158d0c0decf9372420591da - content/104: 21f2e03a976c6fdae0916fc35d0ff6da - content/105: f8b588f1d5f5a10be96cfb20ad48aa8d - content/106: 7895b67ee472b33c29e7b724c12194c2 - content/107: 5b193f9127ffeb27969b0eb77a9430af - content/108: 1f5b6d2e4f9a47eb9158baea95706524 - content/109: 72caa179bccffa9b96f0f8ef2ca0f586 - content/110: e4dccd32b2c9369cd4d30cd844c2d966 - content/111: 3772786592e54266e3b48bacd9d4a56d - content/112: 4c6760fc03981e65675173e761fac54d - content/113: fa75620e72e992981302952a5272a563 - content/114: a6194c147b3f8335a6c2742b9acff4e8 - content/115: 6dfb66af57472bd58ab7dfd6ae55b200 - content/116: d240f8d624a0b76dcf59a349a264fd9b - content/117: d44a76086ce37ddad6e96b3196844107 - content/118: ebf2b3224105b1f684830e34b62f1f55 - content/119: ff1b5b1aa8de35e9afbe19582ab6e869 - content/120: 0bc8fb337ad65d9ebc44d9a1d7dd2ff7 - content/121: 380be3ed5af370f2d36a66f4c55f7f0b - content/122: 2d593b029e19daed6f10c69c0d7759b7 - content/123: 764efc71060244b99a8d638241c9fe0a - content/124: e476fbe8422157871dfcb38fed4179d4 - content/125: 1401fd9898629387a54a4cae02dbcac3 - content/126: 32b6ace25ebb18bed2e07f6a71418a2c - content/127: 480e351bc384141feaf60b1b7005f4ce - content/128: f68a81262dfbea11f6a416580b09b560 - content/129: f2b75ff3cb9bb1773411fd9a23b40bfe - content/130: 72edab923832c1f02354f3b2f28b2c8d - content/131: d670e3e3157fac23c1ae4700d6ed016f - content/132: bf2537ca4fd9c3ee102e68965d99085a - content/133: b5876238045c1b0545fdea9a02afbbed - content/134: acb4a44a6c5a7026b7759ac11a5190e3 - content/135: 868c4213406def92d42d63677c91b3af - content/136: f34a3b1c211eab573cd322b75ca9e588 - content/137: b56e3520e6a6b8cace90ed3394a0e2c0 - content/138: 2db0d84588851b6e374c5a8dba239ce1 - content/139: 245d2df14086f9568a5be76056cc2321 - content/140: c01496d55003667a5ef3aa6f50c74c0b - content/141: d281e696b74d8885dfe93a2b63542cc7 - content/142: 29199935714ad8c4fe4eba8b918317b5 - content/143: f33dc3e3b584d665eccdcd9c19b04e96 - content/144: 36168229df04c0a38c9672a050a6f09c - content/145: 85927f2ffd88a327ea9184dc8b0aadc4 - content/146: 863fd994606a91d7b336123d31a22598 - content/147: 1db961ec80d8fcf5552d00d88ff31c6d - content/148: 984038ab5d384ea5c02da28d8c2d8c38 - content/149: 34ed0a9a3c2a1fb0f375cf4ccd239c2c - content/150: 3b80a9f8cbfd10fc7d7225aff25c0017 - content/151: e0c44df6762ce3227246cfcc87aee7fb - content/152: 10e8e9a59847ddd720c6d06b12c7b120 - content/153: 9799b973234bf2d10d5c971b267f55a5 + content/47: 45abcb9347d57ea0303a8a90ba13034c + content/48: 640d0b31f6faa54914d25c81f5dbf413 + content/49: 97fb14f09ea66ad1636fbe714fc54ca9 + content/50: aebfce781560272a78a1d6777b43636e + content/51: 0bed4847273bc1b883857c1a2dc58e89 + content/52: 58e77d0bc7402a709dbf54c4586208da + content/53: 7d445112e8e8c53a3bb78f3e0e53902d + content/54: 7b0c9986d8c79303926b5293cdb615c7 + content/55: 1421905fe50ea83bf892ed3a36ead4eb + content/56: 4eee624283334d8490037e07c14cc9f2 + content/57: c48e258e50122f0eb0bc8e89d5ad653e + content/58: aa2a6f4894a5d9b0b351702724d1b62b + content/59: 7e45f5de94c34c9dedf5f4636295e4c8 + content/60: a1d30d251f782065bf113889d28034d3 + content/61: 30444d8cb60acb7f30d3c7828e85cd4d + content/62: a03b9429133989d89ee9af6e111d13cc + content/63: 48a7ad3f0849fd9e3f29194c3033294b + content/64: 8ac07d18b175b18a7cc30397cdfc6235 + content/65: 725a7d24863328500e04505306d3f530 + content/66: 6ba587262d22a5b86d7a8ad89a9cd7e6 + content/67: 5d1b2cc1499b5dcd168c5cee8a0eb263 + content/68: 318e0c3aac0a4c2b6ea68d6586af76ed + content/69: 9f481780c40177897b369188ac4e14d1 + content/70: 5b1f5840c84fc15b238be30621c7040d + content/71: 36724b1e1a7d8ede3c3473511e0962c8 + content/72: 7dbb6d2a32807f07f8d15f295d6029c1 + content/73: 94ae1c950a664c7d6a71f543b3590645 + content/74: 959f4ff4a6314dcecc014d943a286a78 + content/75: e1bc33e13fc0ea4ccae263700fb87972 + content/76: e2a8aa98d08edf0385e18206dfcf209c + content/77: 0e4b2f9ac55cbfc1f8478c16fe462461 + content/78: 78a7ec2ac9e6297a1b73b52737d2f703 + content/79: 31209e62b8be12e97e731873a4b5f1dd + content/80: 65a222bfff97f1c2e088bbf2a6e64a98 + content/81: ce50ee3c3c46578e46a5b654992a7184 + content/82: 609fc14a8f76e947f5e0910252a84601 + content/83: 0762a64c782ab06b2497cb1e9bcd040c + content/84: ca93dd4e4bfb89506d0606d5504bb698 + content/85: 70ae6f507d495c0e7f08381093844545 + content/86: faef3168052f65fcae43f4e282c3405a + content/87: 37f44002ca6faa7edbb155f8ff13afe2 + content/88: af0d661c840762599d5bacf04887a063 + content/89: a8e033c5125c9b6035601165bf457168 + content/90: cf2fad1b631722b00fe74c4bc83c1c8f + content/91: da795a2fd9f1b2a97a753e5c7d7a31e0 + content/92: 2899bbcc52d19fd5403c595171e4428a + content/93: 34384a0b1a14658974468de46ae841b9 + content/94: 416104e9a22d7c8eff48d9aaf3a295ce + content/95: 2e210c407617dce99236163adcda8eee + content/96: 3225628004c6c872589b85bb8f01e922 + content/97: b984c1ec400c5b64605c8183fb0de89f + content/98: 0e97ce08c4c8aa23cd7bcb8c178f0526 + content/99: 69bbcf5ddbf3bc375a1c1dc9c0c543b1 + content/100: f4d0a6a649c7cdcdbf44cb505164c246 + content/101: f2b75ff3cb9bb1773411fd9a23b40bfe + content/102: e1b9c5e40f0f7590b6915570836f2ffc + content/103: 840d57f470753b5f90ee96f9664d4488 + content/104: fcf33f9e0158d0c0decf9372420591da + content/105: 21f2e03a976c6fdae0916fc35d0ff6da + content/106: f8b588f1d5f5a10be96cfb20ad48aa8d + content/107: 7895b67ee472b33c29e7b724c12194c2 + content/108: 5b193f9127ffeb27969b0eb77a9430af + content/109: 1f5b6d2e4f9a47eb9158baea95706524 + content/110: 72caa179bccffa9b96f0f8ef2ca0f586 + content/111: e4dccd32b2c9369cd4d30cd844c2d966 + content/112: 3772786592e54266e3b48bacd9d4a56d + content/113: 4c6760fc03981e65675173e761fac54d + content/114: fa75620e72e992981302952a5272a563 + content/115: a6194c147b3f8335a6c2742b9acff4e8 + content/116: 6dfb66af57472bd58ab7dfd6ae55b200 + content/117: d240f8d624a0b76dcf59a349a264fd9b + content/118: d44a76086ce37ddad6e96b3196844107 + content/119: ebf2b3224105b1f684830e34b62f1f55 + content/120: ff1b5b1aa8de35e9afbe19582ab6e869 + content/121: 0bc8fb337ad65d9ebc44d9a1d7dd2ff7 + content/122: 380be3ed5af370f2d36a66f4c55f7f0b + content/123: 2d593b029e19daed6f10c69c0d7759b7 + content/124: 764efc71060244b99a8d638241c9fe0a + content/125: e476fbe8422157871dfcb38fed4179d4 + content/126: 1401fd9898629387a54a4cae02dbcac3 + content/127: 32b6ace25ebb18bed2e07f6a71418a2c + content/128: 480e351bc384141feaf60b1b7005f4ce + content/129: f68a81262dfbea11f6a416580b09b560 + content/130: f2b75ff3cb9bb1773411fd9a23b40bfe + content/131: 72edab923832c1f02354f3b2f28b2c8d + content/132: d670e3e3157fac23c1ae4700d6ed016f + content/133: bf2537ca4fd9c3ee102e68965d99085a + content/134: b5876238045c1b0545fdea9a02afbbed + content/135: acb4a44a6c5a7026b7759ac11a5190e3 + content/136: 868c4213406def92d42d63677c91b3af + content/137: f34a3b1c211eab573cd322b75ca9e588 + content/138: b56e3520e6a6b8cace90ed3394a0e2c0 + content/139: 2db0d84588851b6e374c5a8dba239ce1 + content/140: 245d2df14086f9568a5be76056cc2321 + content/141: c01496d55003667a5ef3aa6f50c74c0b + content/142: d281e696b74d8885dfe93a2b63542cc7 + content/143: 29199935714ad8c4fe4eba8b918317b5 + content/144: f33dc3e3b584d665eccdcd9c19b04e96 + content/145: 36168229df04c0a38c9672a050a6f09c + content/146: 85927f2ffd88a327ea9184dc8b0aadc4 + content/147: 863fd994606a91d7b336123d31a22598 + content/148: 1db961ec80d8fcf5552d00d88ff31c6d + content/149: 984038ab5d384ea5c02da28d8c2d8c38 + content/150: 34ed0a9a3c2a1fb0f375cf4ccd239c2c + content/151: 3b80a9f8cbfd10fc7d7225aff25c0017 + content/152: e0c44df6762ce3227246cfcc87aee7fb + content/153: 10e8e9a59847ddd720c6d06b12c7b120 + content/154: 9799b973234bf2d10d5c971b267f55a5 c25d26c5f6016f2f43987cd7d0b8cd6c: meta/title: e771f23130f9aab4f3e2e99acacf5d5a meta/description: 8539ebe90ff733bef6498db1933f3a57 From 8a5024771e135040dffd9e17ff2cc9862cc1182f Mon Sep 17 00:00:00 2001 From: Charlie Hopkins-Brinicombe Date: Wed, 15 Apr 2026 16:14:36 +0100 Subject: [PATCH 4/7] Revert "Update lock file" This reverts commit fe0e417013e460e7faa340d573544ddbada37c48. --- i18n.lock | 489 +++++++++++++++++++++++++++--------------------------- 1 file changed, 243 insertions(+), 246 deletions(-) diff --git a/i18n.lock b/i18n.lock index 6197593..5eeebe0 100644 --- a/i18n.lock +++ b/i18n.lock @@ -1229,75 +1229,74 @@ checksums: content/47: 6e12b58d5a6d92e53d56ecb9168bacc3 content/48: 640d0b31f6faa54914d25c81f5dbf413 content/49: a3b1e396e4d757d9f716937605f6c513 - content/50: 64ed1b5df7db0c1c23a109495bd2c951 + content/50: f8427fca0863acab3093d7aa878fbfeb content/51: 990ceededf6800238405016bcd11d737 - content/52: 42facdcb08b410e8073ea13de4db8541 - content/53: 640d0b31f6faa54914d25c81f5dbf413 - content/54: 07658aa358af3ef9d173c3f75751a176 - content/55: 88894d9a6f1d49cb0329825bb76e5a17 - content/56: 25b662df6a87d36e4925a6af4247888d - content/57: 374b1eb956e935cf3f51c46af62831ac - content/58: ad94b06eaa66c2fd263362b6af04b9c0 - content/59: c6906582420cbe603d1bb135d7cdc13a - content/60: e6d0829ea90222582df021491c834609 - content/61: c6b7171acaf9ea0be21b408e4beb6c73 - content/62: bbfc8b8ec96997b3e589802f18424196 - content/63: a57adc8d19410c1974f8d59a2e2e1cb9 - content/64: f83c5cc385540bbff0ce0280e31d703d - content/65: 8040541596306a73329cc1f4c375d616 - content/66: c32975398faa1ee29ba3cdfd5bd5e314 - content/67: 0a33871a9ed20bf823cb2c45dc0c91d1 - content/68: 8bff38b0eba94b9ea1b67e7472b74cfc - content/69: 3194ad8ddf9bbe2500b571a9417e2101 - content/70: bec0c223723733b84c83e6d13b637855 - content/71: 1c526016dbe08ff63980dc829cc327f7 - content/72: ce50bb578174216d8d0625907a242263 - content/73: 61f4238f95cc209302563fe0d66aea20 - content/74: 3cc855439498819a71e24a9d44678705 - content/75: dd256e45824b048778e81a0e80a3a6cf - content/76: b384ef84f794014c78bd7232f18d3b5f - content/77: 19f49f0312ddc4ded8f6737083673edf - content/78: 9572e0cb99536535c176db24863ce217 - content/79: 40a3888d076524edcb9716f64683c5b3 - content/80: 041645f33a12c870680e713dc7b8d78a - content/81: 98200505f691d3dcb12fa6833d0e88ad - content/82: 6fecb4eaca40bae0bb37fbe65ff6a637 - content/83: 4455b21dd9cea4b18d0d3991c010eacb - content/84: 61b6140fd37b3d64ac6cf6a5529119a8 - content/85: 539769066341e46b743c0cfda822f3f9 - content/86: c66f09fe4fa88a9e016ebe4f0a2ed9b0 - content/87: 9461907d8be2aa04f16b3d6cae972142 - content/88: 7f85a8c7e5217b02244281defb8e8592 - content/89: 46f1082308b9860120a46a27c8b14892 - content/90: 43e6ec093695c83476ddafd3674f8ec0 - content/91: 5425ae9feefbe5a5ce28290afc321625 - content/92: 853f6a32a56f50b2366cc2e9bfe30cef - content/93: 54dcc7d2d5e4c5ed2c9303950e89ab0d - content/94: aa1e5bc107219b8f73778df6a2eaf649 - content/95: 3898b08aad245d481c00759bba165827 - content/96: 5a26c709955575918c0b6c9093f47410 - content/97: cbb6dd927a2ba8dbe47777babba90548 - content/98: 080834714f41b28fa5e15cc6139d4346 - content/99: 2c595c5d764fd8e4bc386193ea202888 - content/100: 083e769853a4074246e3b140c55e2778 - content/101: a24eeda297d1c1e30583069309251316 - content/102: d860fb4c61d9774d524a4136c86cb598 - content/103: cc20ee2981f70eeb7e0fa675624ef43d - content/104: 29c9df71eb3d97cd840e59bfa6d65acc - content/105: 50c9b9a3be9b72db4c1dbc46fb96e835 - content/106: 8f85d64b4bb8cedd8d9ee8cc962ec075 - content/107: 79524f874744893afda00224a0203ea6 - content/108: 656598f8d7330171f0a3f1f73e884e9f - content/109: eb7a29fcbea23aeecc0192229112e275 - content/110: 25d2f401cdd79bd9c1e761535eb145ce - content/111: 1c813fba4719c7b29863b4944f3dc074 - content/112: 450ad7f99b79b12599856f813b9ef758 - content/113: 39f313714117fbf6ed7b64076ffe1498 - content/114: bb4a3e2c68e0f832f815b659210ef260 - content/115: aae63d8bf6b6da4ac6fb501e13691e4d - content/116: 7a451aee1061c1cf2a4108f13acbafff - content/117: 10e8e9a59847ddd720c6d06b12c7b120 - content/118: 9799b973234bf2d10d5c971b267f55a5 + content/52: 640d0b31f6faa54914d25c81f5dbf413 + content/53: 07658aa358af3ef9d173c3f75751a176 + content/54: 88894d9a6f1d49cb0329825bb76e5a17 + content/55: 25b662df6a87d36e4925a6af4247888d + content/56: 374b1eb956e935cf3f51c46af62831ac + content/57: ad94b06eaa66c2fd263362b6af04b9c0 + content/58: c6906582420cbe603d1bb135d7cdc13a + content/59: e6d0829ea90222582df021491c834609 + content/60: c6b7171acaf9ea0be21b408e4beb6c73 + content/61: bbfc8b8ec96997b3e589802f18424196 + content/62: a57adc8d19410c1974f8d59a2e2e1cb9 + content/63: f83c5cc385540bbff0ce0280e31d703d + content/64: 8040541596306a73329cc1f4c375d616 + content/65: c32975398faa1ee29ba3cdfd5bd5e314 + content/66: 0a33871a9ed20bf823cb2c45dc0c91d1 + content/67: 8bff38b0eba94b9ea1b67e7472b74cfc + content/68: 3194ad8ddf9bbe2500b571a9417e2101 + content/69: bec0c223723733b84c83e6d13b637855 + content/70: 1c526016dbe08ff63980dc829cc327f7 + content/71: ce50bb578174216d8d0625907a242263 + content/72: 61f4238f95cc209302563fe0d66aea20 + content/73: 3cc855439498819a71e24a9d44678705 + content/74: dd256e45824b048778e81a0e80a3a6cf + content/75: b384ef84f794014c78bd7232f18d3b5f + content/76: 19f49f0312ddc4ded8f6737083673edf + content/77: 9572e0cb99536535c176db24863ce217 + content/78: 40a3888d076524edcb9716f64683c5b3 + content/79: 041645f33a12c870680e713dc7b8d78a + content/80: 98200505f691d3dcb12fa6833d0e88ad + content/81: 6fecb4eaca40bae0bb37fbe65ff6a637 + content/82: 4455b21dd9cea4b18d0d3991c010eacb + content/83: 61b6140fd37b3d64ac6cf6a5529119a8 + content/84: 539769066341e46b743c0cfda822f3f9 + content/85: c66f09fe4fa88a9e016ebe4f0a2ed9b0 + content/86: 9461907d8be2aa04f16b3d6cae972142 + content/87: 7f85a8c7e5217b02244281defb8e8592 + content/88: 46f1082308b9860120a46a27c8b14892 + content/89: 43e6ec093695c83476ddafd3674f8ec0 + content/90: 5425ae9feefbe5a5ce28290afc321625 + content/91: 853f6a32a56f50b2366cc2e9bfe30cef + content/92: 54dcc7d2d5e4c5ed2c9303950e89ab0d + content/93: aa1e5bc107219b8f73778df6a2eaf649 + content/94: 3898b08aad245d481c00759bba165827 + content/95: 5a26c709955575918c0b6c9093f47410 + content/96: cbb6dd927a2ba8dbe47777babba90548 + content/97: 080834714f41b28fa5e15cc6139d4346 + content/98: 2c595c5d764fd8e4bc386193ea202888 + content/99: 083e769853a4074246e3b140c55e2778 + content/100: a24eeda297d1c1e30583069309251316 + content/101: d860fb4c61d9774d524a4136c86cb598 + content/102: cc20ee2981f70eeb7e0fa675624ef43d + content/103: 29c9df71eb3d97cd840e59bfa6d65acc + content/104: 50c9b9a3be9b72db4c1dbc46fb96e835 + content/105: 8f85d64b4bb8cedd8d9ee8cc962ec075 + content/106: 79524f874744893afda00224a0203ea6 + content/107: 656598f8d7330171f0a3f1f73e884e9f + content/108: eb7a29fcbea23aeecc0192229112e275 + content/109: 25d2f401cdd79bd9c1e761535eb145ce + content/110: 1c813fba4719c7b29863b4944f3dc074 + content/111: 450ad7f99b79b12599856f813b9ef758 + content/112: 39f313714117fbf6ed7b64076ffe1498 + content/113: bb4a3e2c68e0f832f815b659210ef260 + content/114: aae63d8bf6b6da4ac6fb501e13691e4d + content/115: 7a451aee1061c1cf2a4108f13acbafff + content/116: 10e8e9a59847ddd720c6d06b12c7b120 + content/117: 9799b973234bf2d10d5c971b267f55a5 6a9739e5e2a2b2be16e4da3ab6f64867: meta/title: 230344f713c89383f51389be1e0d3f0d meta/description: 0a913183ce5ee8cfafa21bafe1b0a3e1 @@ -1621,79 +1620,78 @@ checksums: content/33: 6ee187c17478546ee6995ddc7ff71f7d content/34: fd28f573fd8a586b5367a4f9b28e3769 content/35: 5f8179f48cce17a9dfc61a6e5e25635c - content/36: 44e62155a0426ebb1e2cfe3452859c1c + content/36: b1e0b4a77be6bf4155907e2535c08ce8 content/37: 64e7d4b48377ae88bdb94c0526f3ac52 - content/38: 00d6230eeeda9c646035bb68b08682f6 - content/39: 1389ca754bbb08581696ce740b2f7afa + content/38: 8585c7df50d3e4a85a4fdac34176f248 + content/39: 87ffdb6e1e2f3aa7f0988a50eed855ec content/40: 5c144d24f704e1a240b89ab6df4148ba - content/41: 5e38c4efb5d411c77e4b36260df89ce0 + content/41: 5715f85e92401fbe93574ae58e5cd5b7 content/42: d67621c5d4d9fd33358a7ecec0338342 - content/43: 218e40ee0c9c118800a6bbc8c22a0406 + content/43: 18b88784316174b560051890fe13efa0 content/44: 73dc7193c431ee27face3e96cca59471 - content/45: abdc26659c4e48ac2a87a447c8069e21 - content/46: 2da3bce39f011d227e7439bf39000c4b - content/47: 376db24492fd179249a97399cac62c7b - content/48: cbe733e8e67272ce1e1719f34c29ce16 - content/49: be8ee42c61c2f8412a2223346fd1e5d7 - content/50: 19ddbf4a6a27d1217a35ff1ab7082883 - content/51: 6978c15affabcb936aeac6d02d2a9201 - content/52: f53d2373b481a20fe76d332f4a102b84 - content/53: 26f14651033af5a7e0da93c5dd653916 - content/54: 84be083ce8f618a49aa37c1b5ebae0ad - content/55: 29eba1f6a44ec24ee2b0d97faff09564 - content/56: 7fc3a5a4f12e019d5546b6c8688751ce - content/57: 7034f8f1bcdb020342cc70f6ace334b7 - content/58: 610db0f043625ec6ace32150e8b96a66 - content/59: 094e89e6ac397f5e15ea0fc1c209558d - content/60: 20e3ac38d3cc61e817f1dffacaa1c21e - content/61: defd8dab42b430e9687cc7ca90e0052e - content/62: 4dec52ce04aa8849a8a60508baae30ae - content/63: 258c9459be6a0fc3f535b41a89922f50 - content/64: 830481b98e88ae69a3971df3aff5d250 - content/65: 538df8a5de4562fa88ae14bd586adc97 - content/66: f90fc6fdfdcb80d45d9e53b6591d7331 - content/67: b0655e68a1c5d9579fbcfdd75db04fce - content/68: 71d221bcbeb83b663ee8c0a0e4975b04 - content/69: a40d8cc248a02d4ce34ae16119b14bf8 - content/70: 4e5ddca85ed818337a2757ae6bc2dc64 - content/71: a27e8a0a72073744f925f2820a1b7684 - content/72: 73aea3ef20f5f9d1626fcc73fdea597d - content/73: a01a451f7aba1a80f0af7148df811dfc - content/74: 84a204a7da04ff738ebcc5f4d3a5ea35 - content/75: 3347863434fc4a0bd8f075b971428c95 - content/76: bb2ac20c712241859473f415c26a0a11 - content/77: e180f5d5909fef8296047e0782fe8a85 - content/78: 6cc6ea0c653430147746ed145777d8e2 - content/79: 214aa9e0d35236656ba1738c8be4c3f0 - content/80: 919b58f12a4f16d63b5a35d18fdf04bb - content/81: 2ca28a1219570c10ddeaf4cb1157bb6d - content/82: a6390089f6e92430098d3d97c610c17a - content/83: 3710272f77f1cbde0159b940d8826829 - content/84: ff0a8fe413f503e4af629f445746a3dd - content/85: 300bcdb15fbcadfde3db772fafe63524 - content/86: 3d93f05e4ce92565d044836c948352a9 - content/87: 0d08ff106c532b64677561ea2c3bad1f - content/88: 116df75dc256a5019b4809510f35677c - content/89: d6f6ef2bbe6d6ec5e267b33ba910ca7d - content/90: 0ab559de8fc24b6e6cca6175444cfe9a - content/91: fab414b91a7a42cd5e37a0c5ade9945a - content/92: ed80be5f381f63c9293cc5c4182c0632 - content/93: 2d9538daa0f3bcd2d760598097ee5d80 - content/94: 9c3265eff155d4a823fd955cde7dba35 - content/95: ff3d86520f4003c526e008912dbd57fd - content/96: ed10345c824c6fc4c69c882e9983b12b - content/97: fb9ce2e2edb7815efec73b696b5d5e95 - content/98: 25836d79f15e851139e61b1d31fd3c73 - content/99: 44d8170f535788a47c7874e25dedb768 - content/100: fa9f6f1b8a7829a8e68a1d0340a84533 - content/101: 857c1365962ba1b8c02e75f877a5b586 - content/102: 79524f874744893afda00224a0203ea6 - content/103: 0fa45599d835da6f2e916859554673eb - content/104: 450ad7f99b79b12599856f813b9ef758 - content/105: 4f9f49c4756aa59d40fcc76ca15669a9 - content/106: f151f31b2581a7bd7f7d8635bff09dc8 - content/107: 10e8e9a59847ddd720c6d06b12c7b120 - content/108: 9799b973234bf2d10d5c971b267f55a5 + content/45: 2da3bce39f011d227e7439bf39000c4b + content/46: 376db24492fd179249a97399cac62c7b + content/47: cbe733e8e67272ce1e1719f34c29ce16 + content/48: be8ee42c61c2f8412a2223346fd1e5d7 + content/49: 19ddbf4a6a27d1217a35ff1ab7082883 + content/50: 6978c15affabcb936aeac6d02d2a9201 + content/51: f53d2373b481a20fe76d332f4a102b84 + content/52: 26f14651033af5a7e0da93c5dd653916 + content/53: 84be083ce8f618a49aa37c1b5ebae0ad + content/54: 29eba1f6a44ec24ee2b0d97faff09564 + content/55: 7fc3a5a4f12e019d5546b6c8688751ce + content/56: 7034f8f1bcdb020342cc70f6ace334b7 + content/57: 610db0f043625ec6ace32150e8b96a66 + content/58: 094e89e6ac397f5e15ea0fc1c209558d + content/59: 20e3ac38d3cc61e817f1dffacaa1c21e + content/60: defd8dab42b430e9687cc7ca90e0052e + content/61: 4dec52ce04aa8849a8a60508baae30ae + content/62: 258c9459be6a0fc3f535b41a89922f50 + content/63: 830481b98e88ae69a3971df3aff5d250 + content/64: 538df8a5de4562fa88ae14bd586adc97 + content/65: f90fc6fdfdcb80d45d9e53b6591d7331 + content/66: b0655e68a1c5d9579fbcfdd75db04fce + content/67: 71d221bcbeb83b663ee8c0a0e4975b04 + content/68: a40d8cc248a02d4ce34ae16119b14bf8 + content/69: 4e5ddca85ed818337a2757ae6bc2dc64 + content/70: a27e8a0a72073744f925f2820a1b7684 + content/71: 73aea3ef20f5f9d1626fcc73fdea597d + content/72: a01a451f7aba1a80f0af7148df811dfc + content/73: 84a204a7da04ff738ebcc5f4d3a5ea35 + content/74: 3347863434fc4a0bd8f075b971428c95 + content/75: bb2ac20c712241859473f415c26a0a11 + content/76: e180f5d5909fef8296047e0782fe8a85 + content/77: 6cc6ea0c653430147746ed145777d8e2 + content/78: 214aa9e0d35236656ba1738c8be4c3f0 + content/79: 919b58f12a4f16d63b5a35d18fdf04bb + content/80: 2ca28a1219570c10ddeaf4cb1157bb6d + content/81: a6390089f6e92430098d3d97c610c17a + content/82: 3710272f77f1cbde0159b940d8826829 + content/83: ff0a8fe413f503e4af629f445746a3dd + content/84: 300bcdb15fbcadfde3db772fafe63524 + content/85: 3d93f05e4ce92565d044836c948352a9 + content/86: 0d08ff106c532b64677561ea2c3bad1f + content/87: 116df75dc256a5019b4809510f35677c + content/88: d6f6ef2bbe6d6ec5e267b33ba910ca7d + content/89: 0ab559de8fc24b6e6cca6175444cfe9a + content/90: fab414b91a7a42cd5e37a0c5ade9945a + content/91: ed80be5f381f63c9293cc5c4182c0632 + content/92: 2d9538daa0f3bcd2d760598097ee5d80 + content/93: 9c3265eff155d4a823fd955cde7dba35 + content/94: ff3d86520f4003c526e008912dbd57fd + content/95: ed10345c824c6fc4c69c882e9983b12b + content/96: fb9ce2e2edb7815efec73b696b5d5e95 + content/97: 25836d79f15e851139e61b1d31fd3c73 + content/98: 44d8170f535788a47c7874e25dedb768 + content/99: fa9f6f1b8a7829a8e68a1d0340a84533 + content/100: 857c1365962ba1b8c02e75f877a5b586 + content/101: 79524f874744893afda00224a0203ea6 + content/102: 0fa45599d835da6f2e916859554673eb + content/103: 450ad7f99b79b12599856f813b9ef758 + content/104: 4f9f49c4756aa59d40fcc76ca15669a9 + content/105: f151f31b2581a7bd7f7d8635bff09dc8 + content/106: 10e8e9a59847ddd720c6d06b12c7b120 + content/107: 9799b973234bf2d10d5c971b267f55a5 977811daa4a70d47883b868820f2faf6: meta/title: 76712cf76855f695712cf05b994caebd meta/description: 2ff5fa3d67801d9c8ac8e3b3c12d930f @@ -1793,116 +1791,115 @@ checksums: content/42: 640d0b31f6faa54914d25c81f5dbf413 content/43: e466f1ff116db35bc5803115bf80d07f content/44: 5b97682d8de32b1cdf287450ea6589ac - content/45: 19b3d1278e686840d42ac104c3540fdd + content/45: 021b2cc2690448c1c628ab330384609b content/46: a75a0e13bfec5fcaf46f0eab0c4f0c46 - content/47: 45abcb9347d57ea0303a8a90ba13034c - content/48: 640d0b31f6faa54914d25c81f5dbf413 - content/49: 97fb14f09ea66ad1636fbe714fc54ca9 - content/50: aebfce781560272a78a1d6777b43636e - content/51: 0bed4847273bc1b883857c1a2dc58e89 - content/52: 58e77d0bc7402a709dbf54c4586208da - content/53: 7d445112e8e8c53a3bb78f3e0e53902d - content/54: 7b0c9986d8c79303926b5293cdb615c7 - content/55: 1421905fe50ea83bf892ed3a36ead4eb - content/56: 4eee624283334d8490037e07c14cc9f2 - content/57: c48e258e50122f0eb0bc8e89d5ad653e - content/58: aa2a6f4894a5d9b0b351702724d1b62b - content/59: 7e45f5de94c34c9dedf5f4636295e4c8 - content/60: a1d30d251f782065bf113889d28034d3 - content/61: 30444d8cb60acb7f30d3c7828e85cd4d - content/62: a03b9429133989d89ee9af6e111d13cc - content/63: 48a7ad3f0849fd9e3f29194c3033294b - content/64: 8ac07d18b175b18a7cc30397cdfc6235 - content/65: 725a7d24863328500e04505306d3f530 - content/66: 6ba587262d22a5b86d7a8ad89a9cd7e6 - content/67: 5d1b2cc1499b5dcd168c5cee8a0eb263 - content/68: 318e0c3aac0a4c2b6ea68d6586af76ed - content/69: 9f481780c40177897b369188ac4e14d1 - content/70: 5b1f5840c84fc15b238be30621c7040d - content/71: 36724b1e1a7d8ede3c3473511e0962c8 - content/72: 7dbb6d2a32807f07f8d15f295d6029c1 - content/73: 94ae1c950a664c7d6a71f543b3590645 - content/74: 959f4ff4a6314dcecc014d943a286a78 - content/75: e1bc33e13fc0ea4ccae263700fb87972 - content/76: e2a8aa98d08edf0385e18206dfcf209c - content/77: 0e4b2f9ac55cbfc1f8478c16fe462461 - content/78: 78a7ec2ac9e6297a1b73b52737d2f703 - content/79: 31209e62b8be12e97e731873a4b5f1dd - content/80: 65a222bfff97f1c2e088bbf2a6e64a98 - content/81: ce50ee3c3c46578e46a5b654992a7184 - content/82: 609fc14a8f76e947f5e0910252a84601 - content/83: 0762a64c782ab06b2497cb1e9bcd040c - content/84: ca93dd4e4bfb89506d0606d5504bb698 - content/85: 70ae6f507d495c0e7f08381093844545 - content/86: faef3168052f65fcae43f4e282c3405a - content/87: 37f44002ca6faa7edbb155f8ff13afe2 - content/88: af0d661c840762599d5bacf04887a063 - content/89: a8e033c5125c9b6035601165bf457168 - content/90: cf2fad1b631722b00fe74c4bc83c1c8f - content/91: da795a2fd9f1b2a97a753e5c7d7a31e0 - content/92: 2899bbcc52d19fd5403c595171e4428a - content/93: 34384a0b1a14658974468de46ae841b9 - content/94: 416104e9a22d7c8eff48d9aaf3a295ce - content/95: 2e210c407617dce99236163adcda8eee - content/96: 3225628004c6c872589b85bb8f01e922 - content/97: b984c1ec400c5b64605c8183fb0de89f - content/98: 0e97ce08c4c8aa23cd7bcb8c178f0526 - content/99: 69bbcf5ddbf3bc375a1c1dc9c0c543b1 - content/100: f4d0a6a649c7cdcdbf44cb505164c246 - content/101: f2b75ff3cb9bb1773411fd9a23b40bfe - content/102: e1b9c5e40f0f7590b6915570836f2ffc - content/103: 840d57f470753b5f90ee96f9664d4488 - content/104: fcf33f9e0158d0c0decf9372420591da - content/105: 21f2e03a976c6fdae0916fc35d0ff6da - content/106: f8b588f1d5f5a10be96cfb20ad48aa8d - content/107: 7895b67ee472b33c29e7b724c12194c2 - content/108: 5b193f9127ffeb27969b0eb77a9430af - content/109: 1f5b6d2e4f9a47eb9158baea95706524 - content/110: 72caa179bccffa9b96f0f8ef2ca0f586 - content/111: e4dccd32b2c9369cd4d30cd844c2d966 - content/112: 3772786592e54266e3b48bacd9d4a56d - content/113: 4c6760fc03981e65675173e761fac54d - content/114: fa75620e72e992981302952a5272a563 - content/115: a6194c147b3f8335a6c2742b9acff4e8 - content/116: 6dfb66af57472bd58ab7dfd6ae55b200 - content/117: d240f8d624a0b76dcf59a349a264fd9b - content/118: d44a76086ce37ddad6e96b3196844107 - content/119: ebf2b3224105b1f684830e34b62f1f55 - content/120: ff1b5b1aa8de35e9afbe19582ab6e869 - content/121: 0bc8fb337ad65d9ebc44d9a1d7dd2ff7 - content/122: 380be3ed5af370f2d36a66f4c55f7f0b - content/123: 2d593b029e19daed6f10c69c0d7759b7 - content/124: 764efc71060244b99a8d638241c9fe0a - content/125: e476fbe8422157871dfcb38fed4179d4 - content/126: 1401fd9898629387a54a4cae02dbcac3 - content/127: 32b6ace25ebb18bed2e07f6a71418a2c - content/128: 480e351bc384141feaf60b1b7005f4ce - content/129: f68a81262dfbea11f6a416580b09b560 - content/130: f2b75ff3cb9bb1773411fd9a23b40bfe - content/131: 72edab923832c1f02354f3b2f28b2c8d - content/132: d670e3e3157fac23c1ae4700d6ed016f - content/133: bf2537ca4fd9c3ee102e68965d99085a - content/134: b5876238045c1b0545fdea9a02afbbed - content/135: acb4a44a6c5a7026b7759ac11a5190e3 - content/136: 868c4213406def92d42d63677c91b3af - content/137: f34a3b1c211eab573cd322b75ca9e588 - content/138: b56e3520e6a6b8cace90ed3394a0e2c0 - content/139: 2db0d84588851b6e374c5a8dba239ce1 - content/140: 245d2df14086f9568a5be76056cc2321 - content/141: c01496d55003667a5ef3aa6f50c74c0b - content/142: d281e696b74d8885dfe93a2b63542cc7 - content/143: 29199935714ad8c4fe4eba8b918317b5 - content/144: f33dc3e3b584d665eccdcd9c19b04e96 - content/145: 36168229df04c0a38c9672a050a6f09c - content/146: 85927f2ffd88a327ea9184dc8b0aadc4 - content/147: 863fd994606a91d7b336123d31a22598 - content/148: 1db961ec80d8fcf5552d00d88ff31c6d - content/149: 984038ab5d384ea5c02da28d8c2d8c38 - content/150: 34ed0a9a3c2a1fb0f375cf4ccd239c2c - content/151: 3b80a9f8cbfd10fc7d7225aff25c0017 - content/152: e0c44df6762ce3227246cfcc87aee7fb - content/153: 10e8e9a59847ddd720c6d06b12c7b120 - content/154: 9799b973234bf2d10d5c971b267f55a5 + content/47: 640d0b31f6faa54914d25c81f5dbf413 + content/48: 97fb14f09ea66ad1636fbe714fc54ca9 + content/49: aebfce781560272a78a1d6777b43636e + content/50: 0bed4847273bc1b883857c1a2dc58e89 + content/51: 58e77d0bc7402a709dbf54c4586208da + content/52: 7d445112e8e8c53a3bb78f3e0e53902d + content/53: 7b0c9986d8c79303926b5293cdb615c7 + content/54: 1421905fe50ea83bf892ed3a36ead4eb + content/55: 4eee624283334d8490037e07c14cc9f2 + content/56: c48e258e50122f0eb0bc8e89d5ad653e + content/57: aa2a6f4894a5d9b0b351702724d1b62b + content/58: 7e45f5de94c34c9dedf5f4636295e4c8 + content/59: a1d30d251f782065bf113889d28034d3 + content/60: 30444d8cb60acb7f30d3c7828e85cd4d + content/61: a03b9429133989d89ee9af6e111d13cc + content/62: 48a7ad3f0849fd9e3f29194c3033294b + content/63: 8ac07d18b175b18a7cc30397cdfc6235 + content/64: 725a7d24863328500e04505306d3f530 + content/65: 6ba587262d22a5b86d7a8ad89a9cd7e6 + content/66: 5d1b2cc1499b5dcd168c5cee8a0eb263 + content/67: 318e0c3aac0a4c2b6ea68d6586af76ed + content/68: 9f481780c40177897b369188ac4e14d1 + content/69: 5b1f5840c84fc15b238be30621c7040d + content/70: 36724b1e1a7d8ede3c3473511e0962c8 + content/71: 7dbb6d2a32807f07f8d15f295d6029c1 + content/72: 94ae1c950a664c7d6a71f543b3590645 + content/73: 959f4ff4a6314dcecc014d943a286a78 + content/74: e1bc33e13fc0ea4ccae263700fb87972 + content/75: e2a8aa98d08edf0385e18206dfcf209c + content/76: 0e4b2f9ac55cbfc1f8478c16fe462461 + content/77: 78a7ec2ac9e6297a1b73b52737d2f703 + content/78: 31209e62b8be12e97e731873a4b5f1dd + content/79: 65a222bfff97f1c2e088bbf2a6e64a98 + content/80: ce50ee3c3c46578e46a5b654992a7184 + content/81: 609fc14a8f76e947f5e0910252a84601 + content/82: 0762a64c782ab06b2497cb1e9bcd040c + content/83: ca93dd4e4bfb89506d0606d5504bb698 + content/84: 70ae6f507d495c0e7f08381093844545 + content/85: faef3168052f65fcae43f4e282c3405a + content/86: 37f44002ca6faa7edbb155f8ff13afe2 + content/87: af0d661c840762599d5bacf04887a063 + content/88: a8e033c5125c9b6035601165bf457168 + content/89: cf2fad1b631722b00fe74c4bc83c1c8f + content/90: da795a2fd9f1b2a97a753e5c7d7a31e0 + content/91: 2899bbcc52d19fd5403c595171e4428a + content/92: 34384a0b1a14658974468de46ae841b9 + content/93: 416104e9a22d7c8eff48d9aaf3a295ce + content/94: 2e210c407617dce99236163adcda8eee + content/95: 3225628004c6c872589b85bb8f01e922 + content/96: b984c1ec400c5b64605c8183fb0de89f + content/97: 0e97ce08c4c8aa23cd7bcb8c178f0526 + content/98: 69bbcf5ddbf3bc375a1c1dc9c0c543b1 + content/99: f4d0a6a649c7cdcdbf44cb505164c246 + content/100: f2b75ff3cb9bb1773411fd9a23b40bfe + content/101: e1b9c5e40f0f7590b6915570836f2ffc + content/102: 840d57f470753b5f90ee96f9664d4488 + content/103: fcf33f9e0158d0c0decf9372420591da + content/104: 21f2e03a976c6fdae0916fc35d0ff6da + content/105: f8b588f1d5f5a10be96cfb20ad48aa8d + content/106: 7895b67ee472b33c29e7b724c12194c2 + content/107: 5b193f9127ffeb27969b0eb77a9430af + content/108: 1f5b6d2e4f9a47eb9158baea95706524 + content/109: 72caa179bccffa9b96f0f8ef2ca0f586 + content/110: e4dccd32b2c9369cd4d30cd844c2d966 + content/111: 3772786592e54266e3b48bacd9d4a56d + content/112: 4c6760fc03981e65675173e761fac54d + content/113: fa75620e72e992981302952a5272a563 + content/114: a6194c147b3f8335a6c2742b9acff4e8 + content/115: 6dfb66af57472bd58ab7dfd6ae55b200 + content/116: d240f8d624a0b76dcf59a349a264fd9b + content/117: d44a76086ce37ddad6e96b3196844107 + content/118: ebf2b3224105b1f684830e34b62f1f55 + content/119: ff1b5b1aa8de35e9afbe19582ab6e869 + content/120: 0bc8fb337ad65d9ebc44d9a1d7dd2ff7 + content/121: 380be3ed5af370f2d36a66f4c55f7f0b + content/122: 2d593b029e19daed6f10c69c0d7759b7 + content/123: 764efc71060244b99a8d638241c9fe0a + content/124: e476fbe8422157871dfcb38fed4179d4 + content/125: 1401fd9898629387a54a4cae02dbcac3 + content/126: 32b6ace25ebb18bed2e07f6a71418a2c + content/127: 480e351bc384141feaf60b1b7005f4ce + content/128: f68a81262dfbea11f6a416580b09b560 + content/129: f2b75ff3cb9bb1773411fd9a23b40bfe + content/130: 72edab923832c1f02354f3b2f28b2c8d + content/131: d670e3e3157fac23c1ae4700d6ed016f + content/132: bf2537ca4fd9c3ee102e68965d99085a + content/133: b5876238045c1b0545fdea9a02afbbed + content/134: acb4a44a6c5a7026b7759ac11a5190e3 + content/135: 868c4213406def92d42d63677c91b3af + content/136: f34a3b1c211eab573cd322b75ca9e588 + content/137: b56e3520e6a6b8cace90ed3394a0e2c0 + content/138: 2db0d84588851b6e374c5a8dba239ce1 + content/139: 245d2df14086f9568a5be76056cc2321 + content/140: c01496d55003667a5ef3aa6f50c74c0b + content/141: d281e696b74d8885dfe93a2b63542cc7 + content/142: 29199935714ad8c4fe4eba8b918317b5 + content/143: f33dc3e3b584d665eccdcd9c19b04e96 + content/144: 36168229df04c0a38c9672a050a6f09c + content/145: 85927f2ffd88a327ea9184dc8b0aadc4 + content/146: 863fd994606a91d7b336123d31a22598 + content/147: 1db961ec80d8fcf5552d00d88ff31c6d + content/148: 984038ab5d384ea5c02da28d8c2d8c38 + content/149: 34ed0a9a3c2a1fb0f375cf4ccd239c2c + content/150: 3b80a9f8cbfd10fc7d7225aff25c0017 + content/151: e0c44df6762ce3227246cfcc87aee7fb + content/152: 10e8e9a59847ddd720c6d06b12c7b120 + content/153: 9799b973234bf2d10d5c971b267f55a5 c25d26c5f6016f2f43987cd7d0b8cd6c: meta/title: e771f23130f9aab4f3e2e99acacf5d5a meta/description: 8539ebe90ff733bef6498db1933f3a57 From 761aa79abb062d05a0bc0e62c8e1541ccdc293c4 Mon Sep 17 00:00:00 2001 From: Charlie Hopkins-Brinicombe Date: Wed, 15 Apr 2026 22:32:30 +0100 Subject: [PATCH 5/7] Use staging bucket for translations for ability to hose en at root, which breaks Lingo default pattern. Without this Lingo tries to translate source files. --- .gitignore | 3 +- i18n.json | 112 +++++-------------- openapi.yml | 172 +++++++++++------------------- scripts/generate-translations.mjs | 149 +++++++++++++++++++++++--- scripts/validate-translations.mjs | 80 ++++++++++++++ 5 files changed, 306 insertions(+), 210 deletions(-) diff --git a/.gitignore b/.gitignore index 9bdf355..08500c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .DS_Store -.env \ No newline at end of file +.env +.tmp \ No newline at end of file diff --git a/i18n.json b/i18n.json index 89bec61..d0dec87 100644 --- a/i18n.json +++ b/i18n.json @@ -10,91 +10,33 @@ "buckets": { "mdx": { "include": [ - "account/billing.mdx", - "account/branding.mdx", - "account/members.mdx", - "account/overview.mdx", - "admin-api/endpoints/points/archive-a-boost.mdx", - "admin-api/endpoints/points/archive-boosts-batch.mdx", - "admin-api/endpoints/points/create-boosts.mdx", - "admin-api/endpoints/streaks/grant-freezes.mdx", - "admin-api/endpoints/streaks/restore-streaks.mdx", - "admin-api/introduction.mdx", - "api-reference/authentication.mdx", - "api-reference/client-libraries.mdx", - "api-reference/endpoints/achievements/all-achievements.mdx", - "api-reference/endpoints/achievements/mark-an-achievement-as-completed.mdx", - "api-reference/endpoints/leaderboards/get-all-active-leaderboards.mdx", - "api-reference/endpoints/leaderboards/get-leaderboard.mdx", - "api-reference/endpoints/metrics/send-a-metric-change-event.mdx", - "api-reference/endpoints/points/get-points-boosts.mdx", - "api-reference/endpoints/points/get-points-level-summary.mdx", - "api-reference/endpoints/points/get-points-levels.mdx", - "api-reference/endpoints/points/get-points-summary.mdx", - "api-reference/endpoints/points/get-points.mdx", - "api-reference/endpoints/streaks/get-streak-rankings.mdx", - "api-reference/endpoints/streaks/get-streaks.mdx", - "api-reference/endpoints/users/create-a-user.mdx", - "api-reference/endpoints/users/get-a-single-metric-event-summary-for-a-user.mdx", - "api-reference/endpoints/users/get-a-single-metric-for-a-user.mdx", - "api-reference/endpoints/users/get-a-single-user.mdx", - "api-reference/endpoints/users/get-a-users-completed-achievements.mdx", - "api-reference/endpoints/users/get-a-users-leaderboard.mdx", - "api-reference/endpoints/users/get-a-users-points-boosts.mdx", - "api-reference/endpoints/users/get-a-users-points-summary.mdx", - "api-reference/endpoints/users/get-a-users-points.mdx", - "api-reference/endpoints/users/get-a-users-streak.mdx", - "api-reference/endpoints/users/get-a-users-wrapped.mdx", - "api-reference/endpoints/users/get-all-metrics-for-a-user.mdx", - "api-reference/endpoints/users/get-user-preferences.mdx", - "api-reference/endpoints/users/identify-a-user.mdx", - "api-reference/endpoints/users/update-a-user.mdx", - "api-reference/endpoints/users/update-user-preferences.mdx", - "api-reference/idempotency.mdx", - "api-reference/introduction.mdx", - "api-reference/rate-limiting.mdx", - "experimentation/engagement.mdx", - "experimentation/overview.mdx", - "experimentation/retention.mdx", - "getting-started/introduction.mdx", - "getting-started/quickstart.mdx", - "guides/gamified-fitness-platform.mdx", - "guides/gamified-study-platform.mdx", - "guides/how-to-build-a-leaderboards-feature.mdx", - "guides/how-to-build-a-streaks-feature.mdx", - "guides/how-to-build-an-achievements-feature.mdx", - "guides/how-to-build-an-energy-feature.mdx", - "guides/how-to-build-an-xp-feature.mdx", - "platform/achievements.mdx", - "platform/emails.mdx", - "platform/events.mdx", - "platform/leaderboards.mdx", - "platform/metrics.mdx", - "platform/overview.mdx", - "platform/points.mdx", - "platform/push-notifications.mdx", - "platform/streaks.mdx", - "platform/users.mdx", - "webhooks/events/achievements/achievement-completed.mdx", - "webhooks/events/leaderboards/leaderboard-changed.mdx", - "webhooks/events/leaderboards/leaderboard-finished.mdx", - "webhooks/events/leaderboards/leaderboard-rank-changed.mdx", - "webhooks/events/leaderboards/leaderboard-started.mdx", - "webhooks/events/points/points-boost-finished.mdx", - "webhooks/events/points/points-boost-started.mdx", - "webhooks/events/points/points-changed.mdx", - "webhooks/events/points/points-level-changed.mdx", - "webhooks/events/streaks/streak-extended.mdx", - "webhooks/events/streaks/streak-freeze-consumed.mdx", - "webhooks/events/streaks/streak-freeze-earned.mdx", - "webhooks/events/streaks/streak-lost.mdx", - "webhooks/events/streaks/streak-started.mdx", - "webhooks/idempotency.mdx", - "webhooks/introduction.mdx", - "webhooks/observability.mdx", - "webhooks/quickstart.mdx", - "webhooks/retries.mdx", - "webhooks/security.mdx" + "account/*.mdx", + "admin-api/*.mdx", + "admin-api/endpoints/points/*.mdx", + "admin-api/endpoints/streaks/*.mdx", + "api-reference/*.mdx", + "api-reference/endpoints/achievements/*.mdx", + "api-reference/endpoints/leaderboards/*.mdx", + "api-reference/endpoints/metrics/*.mdx", + "api-reference/endpoints/points/*.mdx", + "api-reference/endpoints/streaks/*.mdx", + "api-reference/endpoints/users/*.mdx", + "experimentation/*.mdx", + "getting-started/*.mdx", + "guides/*.mdx", + "platform/*.mdx", + "webhooks/*.mdx", + "webhooks/events/achievements/*.mdx", + "webhooks/events/leaderboards/*.mdx", + "webhooks/events/points/*.mdx", + "webhooks/events/streaks/*.mdx" + ], + "exclude": [ + "snippets/*.mdx", + "es/*.mdx", + "es/*/*.mdx", + "es/*/*/*.mdx", + "es/*/*/*/*.mdx" ] } } diff --git a/openapi.yml b/openapi.yml index 0887c31..30e1e6d 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: Trophy - version: '1.3.3' + version: '1.3.4' paths: # APPLICATION API ------------------------------------------------------- @@ -4592,6 +4592,16 @@ components: points: type: integer description: The points awarded by this trigger. + status: + type: string + enum: ['active', 'inactive', 'archived'] + description: The status of the trigger. + achievementId: + type: string + description: The unique ID of the achievement associated with this trigger, if the trigger is an achievement. + metricId: + type: string + description: The unique ID of the metric associated with this trigger, if the trigger is a metric. metricName: type: string description: If the trigger has type 'metric', the name of the metric @@ -4611,6 +4621,39 @@ components: timeInterval: type: integer description: If the trigger has type 'time', the numer of units of timeUnit after which to award points + userAttributes: + type: array + description: User attribute filters that must be met for this trigger to award points. Empty when the trigger has no user attribute filters configured. + items: + type: object + properties: + key: + type: string + description: The key of the user attribute. + example: plan-type + value: + type: string + description: The required value of the user attribute. + example: premium + required: + - key + - value + eventAttribute: + type: object + description: Deprecated. Event attribute filter that must be met for this trigger to award points. Only present if the trigger has an event filter configured. + deprecated: true + properties: + key: + type: string + description: The key of the event attribute. + example: source + value: + type: string + description: The required value of the event attribute. + example: mobile-app + required: + - key + - value eventAttributes: type: array description: If the trigger has type 'metric', the event attributes that must match for the trigger to award points. Empty when the trigger is metric-based and has no event attribute filters. Omitted for non-metric triggers. @@ -4628,6 +4671,22 @@ components: required: - key - value + created: + type: string + format: date-time + description: The date and time the trigger was created, in ISO 8601 format. + updated: + type: string + format: date-time + description: The date and time the trigger was last updated, in ISO 8601 format. + required: + - id + - type + - points + - status + - userAttributes + - created + - updated PointsAward: title: PointsAward type: object @@ -5547,115 +5606,6 @@ components: power of 10. items: $ref: '#/components/schemas/PointsRange' - PointsTriggerResponse: - title: PointsTriggerResponse - type: object - properties: - id: - type: string - description: The unique ID of the trigger. - type: - type: string - enum: ['metric', 'achievement', 'streak', 'time', 'user_creation'] - description: The type of trigger. - points: - type: integer - description: The points awarded by this trigger. - status: - type: string - enum: ['active', 'archived'] - description: The status of the trigger. - achievementId: - type: string - description: The unique ID of the achievement associated with this trigger, if the trigger is an achievement. - metricId: - type: string - description: The unique ID of the metric associated with this trigger, if the trigger is a metric. - metricThreshold: - type: integer - description: The amount that a user must increase the metric to earn the points, if the trigger is a metric. - streakLengthThreshold: - type: integer - description: The number of consecutive streak periods that a user must complete to earn the points, if the trigger is a streak. - metricName: - type: string - description: The name of the metric associated with this trigger, if the trigger is a metric. - achievementName: - type: string - description: The name of the achievement associated with this trigger, if the trigger is an achievement. - timeUnit: - type: string - enum: ['hour', 'day'] - description: The time unit of the trigger, if the trigger is a time interval. - timeInterval: - type: integer - description: The interval of the trigger in the time unit, if the trigger is a time interval. - userAttributes: - type: array - description: User attribute filters that must be met for this trigger to activate. - items: - type: object - properties: - key: - type: string - description: The key of the user attribute. - example: plan-type - value: - type: string - description: The value of the user attribute. - example: premium - required: - - key - - value - eventAttribute: - type: object - description: Deprecated. Event attribute filter that must be met for this trigger to activate. Only present if the trigger has an event filter configured. - deprecated: true - properties: - key: - type: string - description: The key of the event attribute. - example: source - value: - type: string - description: The required value of the event attribute. - example: mobile-app - required: - - key - - value - eventAttributes: - type: array - description: Event attribute filters that must be met for this trigger to activate. Present when the trigger uses one or more event attribute filters. - items: - type: object - properties: - key: - type: string - description: The key of the event attribute. - example: source - value: - type: string - description: The required value of the event attribute. - example: mobile-app - required: - - key - - value - created: - type: string - format: date-time - description: The date and time the trigger was created, in ISO 8601 format. - updated: - type: string - format: date-time - description: The date and time the trigger was last updated, in ISO 8601 format. - required: - - id - - type - - points - - status - - userAttributes - - created - - updated PointsSystemResponse: title: PointsSystemResponse type: object @@ -5685,7 +5635,7 @@ components: type: array description: Array of active triggers for this points system. items: - $ref: '#/components/schemas/PointsTriggerResponse' + $ref: '#/components/schemas/PointsTrigger' required: - id - name diff --git a/scripts/generate-translations.mjs b/scripts/generate-translations.mjs index 36e78b6..3ecf697 100644 --- a/scripts/generate-translations.mjs +++ b/scripts/generate-translations.mjs @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; +import crypto from "node:crypto"; import { execSync } from "node:child_process"; const args = process.argv.slice(2); @@ -14,6 +15,114 @@ function getArg(name) { function hasFlag(name) { return args.includes(name); } +function toPosix(p) { + return p.replace(/\\/g, "/"); +} +function ensureDirForFile(filePath) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); +} +function escapeRegex(s) { + return s.replace(/[|\\{}()[\]^$+?.]/g, "\\$&"); +} +function globToRegex(glob) { + return new RegExp(`^${escapeRegex(toPosix(glob)).replace(/\*/g, "[^/]*")}$`); +} +function hashFile(filePath) { + const buf = fs.readFileSync(filePath); + return crypto.createHash("sha256").update(buf).digest("hex"); +} +function listSourceMdxFiles(root, sourceLocale, targetLocales) { + const excludedTop = new Set([ + sourceLocale, + ...targetLocales, + ".git", + ".github", + "node_modules", + "scripts", + "snippets", + "lingo", + "styles", + ".cursor", + ".tmp", + ]); + const out = []; + const stack = [root]; + while (stack.length) { + const current = stack.pop(); + const entries = fs.readdirSync(current, { withFileTypes: true }); + for (const entry of entries) { + const abs = path.join(current, entry.name); + const rel = toPosix(path.relative(root, abs)); + if (entry.isDirectory()) { + if (current === root && excludedTop.has(entry.name)) continue; + stack.push(abs); + } else if (entry.isFile() && rel.endsWith(".mdx")) { + out.push(rel); + } + } + } + return out.sort(); +} +function extractMdxIncludePatterns(config) { + const include = Array.isArray(config?.buckets?.mdx?.include) ? config.buckets.mdx.include : []; + return include + .map((entry) => { + if (typeof entry === "string") return entry; + if (entry && typeof entry.path === "string") return entry.path; + return null; + }) + .filter(Boolean) + .map((p) => toPosix(p)); +} +function resolveSourceFiles(config, sourceLocale, targetLocales) { + const patterns = extractMdxIncludePatterns(config); + if (patterns.length === 0) return []; + const allSourceMdx = listSourceMdxFiles(process.cwd(), sourceLocale, targetLocales); + const matchers = patterns.map(globToRegex); + return allSourceMdx.filter((rel) => matchers.some((re) => re.test(rel))); +} +function buildSourceHashSnapshot(sourceFiles) { + const snapshot = new Map(); + for (const rel of sourceFiles) { + const abs = path.join(process.cwd(), rel); + snapshot.set(rel, hashFile(abs)); + } + return snapshot; +} +function assertSourceUnchanged(sourceHashes) { + const changed = []; + for (const [rel, before] of sourceHashes.entries()) { + const abs = path.join(process.cwd(), rel); + if (!fs.existsSync(abs) || hashFile(abs) !== before) changed.push(rel); + } + if (changed.length > 0) { + throw new Error( + `Source files were unexpectedly modified during translation: ${changed.slice(0, 10).join(", ")}${changed.length > 10 ? " ..." : ""}` + ); + } +} +function writeStagedFilesForTarget(stagingRoot, sourceLocale, target, sourceFiles) { + for (const rel of sourceFiles) { + const sourcePath = path.join(process.cwd(), rel); + const stagedSource = path.join(process.cwd(), stagingRoot, sourceLocale, rel); + ensureDirForFile(stagedSource); + fs.copyFileSync(sourcePath, stagedSource); + + const existingTargetPath = path.join(process.cwd(), target, rel); + const stagedTarget = path.join(process.cwd(), stagingRoot, target, rel); + ensureDirForFile(stagedTarget); + fs.copyFileSync(fs.existsSync(existingTargetPath) ? existingTargetPath : sourcePath, stagedTarget); + } +} +function copyStagedTargetToLocale(stagingRoot, target, sourceFiles) { + for (const rel of sourceFiles) { + const stagedTarget = path.join(process.cwd(), stagingRoot, target, rel); + if (!fs.existsSync(stagedTarget)) continue; + const destination = path.join(process.cwd(), target, rel); + ensureDirForFile(destination); + fs.copyFileSync(stagedTarget, destination); + } +} const targetArg = getArg("--target"); const pathFilter = getArg("--paths"); @@ -27,6 +136,7 @@ const filters = pathFilter const configPath = "i18n.json"; const backupPath = "i18n.json.bak"; +const stagingRoot = toPosix(path.join(".tmp", "lingo")); if (!fs.existsSync(configPath)) { console.error("Missing i18n.json"); @@ -40,6 +150,7 @@ if (!cfg.locale || !cfg.locale.source || !cfg.buckets) { console.error("Invalid i18n.json: locale.source and buckets are required."); process.exit(1); } +const sourceLocale = cfg.locale.source; const configuredTargets = Array.isArray(cfg.locale.targets) ? [...cfg.locale.targets] : []; const engineIdFromEnv = process.env.LINGO_ENGINE_ID; @@ -51,17 +162,15 @@ if (!cfg.engineId || cfg.engineId === "${LINGO_ENGINE_ID}") { process.exit(1); } -if (filters.length > 0 && cfg.buckets) { - let matched = 0; - for (const bucket of Object.values(cfg.buckets)) { - if (!bucket?.include) continue; - bucket.include = bucket.include.filter((pattern) => { - const keep = filters.some((f) => pattern.includes(f)); - if (keep) matched += 1; - return keep; - }); - } - if (matched === 0) { +let sourceFiles = resolveSourceFiles(cfg, sourceLocale, configuredTargets); +if (sourceFiles.length === 0) { + console.error("No source MDX files resolved from i18n.json buckets.mdx.include."); + process.exit(1); +} + +if (filters.length > 0) { + sourceFiles = sourceFiles.filter((rel) => filters.some((f) => rel.includes(f))); + if (sourceFiles.length === 0) { console.error("No files matched --paths filters."); process.exit(1); } @@ -83,8 +192,18 @@ if (targets.length === 0) { try { execSync("node scripts/sync-openapi-titles.mjs", { stdio: "inherit" }); for (const target of targets) { - cfg.locale.targets = [target]; - fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n"); + fs.rmSync(path.join(process.cwd(), stagingRoot), { recursive: true, force: true }); + writeStagedFilesForTarget(stagingRoot, sourceLocale, target, sourceFiles); + + const sourceHashes = buildSourceHashSnapshot(sourceFiles); + const runCfg = JSON.parse(JSON.stringify(cfg)); + runCfg.locale.targets = [target]; + if (!runCfg.buckets?.mdx) { + throw new Error("Invalid i18n.json: buckets.mdx is required for staging workflow."); + } + runCfg.buckets.mdx.include = sourceFiles.map((rel) => toPosix(path.join(stagingRoot, "[locale]", rel))); + fs.writeFileSync(configPath, JSON.stringify(runCfg, null, 2) + "\n"); + // Lingo prints "from cache" when per-file work is *skipped* because there is nothing in // `processableData` (delta empty vs lock). `run --force` forces every key into that set and // passes empty targetData into the localizer for fresh output (see lingo.dev CLI). Purge first @@ -96,6 +215,9 @@ try { } const runForce = forceRetranslate ? " --force" : ""; execSync(`npx lingo.dev@latest run --target-locale ${target}${runForce}`, { stdio: "inherit" }); + assertSourceUnchanged(sourceHashes); + copyStagedTargetToLocale(stagingRoot, target, sourceFiles); + execSync(`node scripts/translate-docs-json.mjs --target ${target}`, { stdio: "inherit" }); execSync(`node scripts/localize-internal-links.mjs --target ${target}`, { stdio: "inherit" }); execSync(`node scripts/localize-mdx-paths.mjs --target ${target}`, { stdio: "inherit" }); @@ -103,6 +225,7 @@ try { } execSync("node scripts/sync-heading-anchors.mjs", { stdio: "inherit" }); } finally { + fs.rmSync(path.join(process.cwd(), stagingRoot), { recursive: true, force: true }); if (fs.existsSync(backupPath)) { fs.copyFileSync(backupPath, configPath); fs.unlinkSync(backupPath); diff --git a/scripts/validate-translations.mjs b/scripts/validate-translations.mjs index a27b16e..75f9951 100644 --- a/scripts/validate-translations.mjs +++ b/scripts/validate-translations.mjs @@ -2,12 +2,26 @@ import fs from "node:fs"; import path from "node:path"; +import crypto from "node:crypto"; import { parse as parseYaml } from "yaml"; +const args = process.argv.slice(2); +function getArg(name) { + const idx = args.indexOf(name); + if (idx === -1) return null; + return args[idx + 1] ?? null; +} +function hasFlag(name) { + return args.includes(name); +} + const ROOT = process.cwd(); const CONFIG = JSON.parse(fs.readFileSync(path.join(ROOT, "i18n.json"), "utf8")); const SOURCE_LOCALE = CONFIG.locale?.source || "en"; const TARGET_LOCALES = Array.isArray(CONFIG.locale?.targets) ? CONFIG.locale.targets : []; +const sourceSnapshotPath = getArg("--source-hash-snapshot"); +const changedSourceListPath = getArg("--changed-source-list"); +const checkSourceLocaleHeuristic = hasFlag("--check-source-locale-heuristic"); const EXCLUDED_SOURCE_TOP_DIRS = new Set([ SOURCE_LOCALE, ...TARGET_LOCALES, @@ -61,8 +75,51 @@ function fail(msg) { process.exitCode = 1; } +function hashContent(content) { + return crypto.createHash("sha256").update(content).digest("hex"); +} + +function readJsonIfExists(filePath) { + if (!filePath) return null; + const abs = path.isAbsolute(filePath) ? filePath : path.join(ROOT, filePath); + if (!fs.existsSync(abs)) return null; + return JSON.parse(fs.readFileSync(abs, "utf8")); +} + +function readListIfExists(filePath) { + if (!filePath) return []; + const abs = path.isAbsolute(filePath) ? filePath : path.join(ROOT, filePath); + if (!fs.existsSync(abs)) return []; + return fs.readFileSync(abs, "utf8") + .split("\n") + .map((s) => s.trim()) + .filter(Boolean); +} + +function looksLocalizedAwayFromEnglish(content) { + const accented = /[áéíóúñ¿¡]/i.test(content); + const commonSpanishWords = /\b(para|usuarios|puntos|logros|racha|nivel|ayuda|activar|guardar)\b/i.test(content); + return accented && commonSpanishWords; +} + const srcFiles = walkRootMdx(ROOT); const srcSet = new Set(srcFiles.map((f) => path.relative(ROOT, f).replace(/\\/g, "/"))); +const sourceSnapshot = readJsonIfExists(sourceSnapshotPath); +if (sourceSnapshot) { + for (const [rel, expectedHash] of Object.entries(sourceSnapshot)) { + const sourcePath = path.join(ROOT, rel); + if (!fs.existsSync(sourcePath)) { + fail(`Source snapshot file missing from workspace: ${rel}`); + continue; + } + const currentHash = hashContent(fs.readFileSync(sourcePath, "utf8")); + if (currentHash !== expectedHash) { + fail(`Source file changed unexpectedly since snapshot: ${rel}`); + } + } +} + +const changedSourceList = readListIfExists(changedSourceListPath); for (const TARGET_LOCALE of TARGET_LOCALES) { const dstDir = path.join(ROOT, TARGET_LOCALE); @@ -106,6 +163,29 @@ for (const TARGET_LOCALE of TARGET_LOCALES) { fail(`Missing translated description in ${TARGET_LOCALE}/${rel}`); } } + + if (changedSourceList.length > 0) { + for (const rel of changedSourceList) { + const srcPath = path.join(ROOT, rel); + const dstPath = path.join(dstDir, rel); + if (!fs.existsSync(srcPath) || !fs.existsSync(dstPath)) continue; + const srcContent = fs.readFileSync(srcPath, "utf8"); + const dstContent = fs.readFileSync(dstPath, "utf8"); + if (hashContent(srcContent) === hashContent(dstContent)) { + fail(`Changed source appears untranslated in ${TARGET_LOCALE}/${rel}`); + } + } + } +} + +if (checkSourceLocaleHeuristic) { + for (const rel of srcSet) { + const srcPath = path.join(ROOT, rel); + const srcContent = fs.readFileSync(srcPath, "utf8"); + if (looksLocalizedAwayFromEnglish(srcContent)) { + fail(`Source locale drift heuristic triggered for ${rel}`); + } + } } if (process.exitCode) { From ecdbd97a38b01b5f42cf73dbcc29d5792a664c65 Mon Sep 17 00:00:00 2001 From: Charlie Hopkins-Brinicombe Date: Wed, 15 Apr 2026 22:34:34 +0100 Subject: [PATCH 6/7] Update lock --- i18n.lock | 489 +++++++++++++++++++++++++++--------------------------- 1 file changed, 246 insertions(+), 243 deletions(-) diff --git a/i18n.lock b/i18n.lock index 5eeebe0..6197593 100644 --- a/i18n.lock +++ b/i18n.lock @@ -1229,74 +1229,75 @@ checksums: content/47: 6e12b58d5a6d92e53d56ecb9168bacc3 content/48: 640d0b31f6faa54914d25c81f5dbf413 content/49: a3b1e396e4d757d9f716937605f6c513 - content/50: f8427fca0863acab3093d7aa878fbfeb + content/50: 64ed1b5df7db0c1c23a109495bd2c951 content/51: 990ceededf6800238405016bcd11d737 - content/52: 640d0b31f6faa54914d25c81f5dbf413 - content/53: 07658aa358af3ef9d173c3f75751a176 - content/54: 88894d9a6f1d49cb0329825bb76e5a17 - content/55: 25b662df6a87d36e4925a6af4247888d - content/56: 374b1eb956e935cf3f51c46af62831ac - content/57: ad94b06eaa66c2fd263362b6af04b9c0 - content/58: c6906582420cbe603d1bb135d7cdc13a - content/59: e6d0829ea90222582df021491c834609 - content/60: c6b7171acaf9ea0be21b408e4beb6c73 - content/61: bbfc8b8ec96997b3e589802f18424196 - content/62: a57adc8d19410c1974f8d59a2e2e1cb9 - content/63: f83c5cc385540bbff0ce0280e31d703d - content/64: 8040541596306a73329cc1f4c375d616 - content/65: c32975398faa1ee29ba3cdfd5bd5e314 - content/66: 0a33871a9ed20bf823cb2c45dc0c91d1 - content/67: 8bff38b0eba94b9ea1b67e7472b74cfc - content/68: 3194ad8ddf9bbe2500b571a9417e2101 - content/69: bec0c223723733b84c83e6d13b637855 - content/70: 1c526016dbe08ff63980dc829cc327f7 - content/71: ce50bb578174216d8d0625907a242263 - content/72: 61f4238f95cc209302563fe0d66aea20 - content/73: 3cc855439498819a71e24a9d44678705 - content/74: dd256e45824b048778e81a0e80a3a6cf - content/75: b384ef84f794014c78bd7232f18d3b5f - content/76: 19f49f0312ddc4ded8f6737083673edf - content/77: 9572e0cb99536535c176db24863ce217 - content/78: 40a3888d076524edcb9716f64683c5b3 - content/79: 041645f33a12c870680e713dc7b8d78a - content/80: 98200505f691d3dcb12fa6833d0e88ad - content/81: 6fecb4eaca40bae0bb37fbe65ff6a637 - content/82: 4455b21dd9cea4b18d0d3991c010eacb - content/83: 61b6140fd37b3d64ac6cf6a5529119a8 - content/84: 539769066341e46b743c0cfda822f3f9 - content/85: c66f09fe4fa88a9e016ebe4f0a2ed9b0 - content/86: 9461907d8be2aa04f16b3d6cae972142 - content/87: 7f85a8c7e5217b02244281defb8e8592 - content/88: 46f1082308b9860120a46a27c8b14892 - content/89: 43e6ec093695c83476ddafd3674f8ec0 - content/90: 5425ae9feefbe5a5ce28290afc321625 - content/91: 853f6a32a56f50b2366cc2e9bfe30cef - content/92: 54dcc7d2d5e4c5ed2c9303950e89ab0d - content/93: aa1e5bc107219b8f73778df6a2eaf649 - content/94: 3898b08aad245d481c00759bba165827 - content/95: 5a26c709955575918c0b6c9093f47410 - content/96: cbb6dd927a2ba8dbe47777babba90548 - content/97: 080834714f41b28fa5e15cc6139d4346 - content/98: 2c595c5d764fd8e4bc386193ea202888 - content/99: 083e769853a4074246e3b140c55e2778 - content/100: a24eeda297d1c1e30583069309251316 - content/101: d860fb4c61d9774d524a4136c86cb598 - content/102: cc20ee2981f70eeb7e0fa675624ef43d - content/103: 29c9df71eb3d97cd840e59bfa6d65acc - content/104: 50c9b9a3be9b72db4c1dbc46fb96e835 - content/105: 8f85d64b4bb8cedd8d9ee8cc962ec075 - content/106: 79524f874744893afda00224a0203ea6 - content/107: 656598f8d7330171f0a3f1f73e884e9f - content/108: eb7a29fcbea23aeecc0192229112e275 - content/109: 25d2f401cdd79bd9c1e761535eb145ce - content/110: 1c813fba4719c7b29863b4944f3dc074 - content/111: 450ad7f99b79b12599856f813b9ef758 - content/112: 39f313714117fbf6ed7b64076ffe1498 - content/113: bb4a3e2c68e0f832f815b659210ef260 - content/114: aae63d8bf6b6da4ac6fb501e13691e4d - content/115: 7a451aee1061c1cf2a4108f13acbafff - content/116: 10e8e9a59847ddd720c6d06b12c7b120 - content/117: 9799b973234bf2d10d5c971b267f55a5 + content/52: 42facdcb08b410e8073ea13de4db8541 + content/53: 640d0b31f6faa54914d25c81f5dbf413 + content/54: 07658aa358af3ef9d173c3f75751a176 + content/55: 88894d9a6f1d49cb0329825bb76e5a17 + content/56: 25b662df6a87d36e4925a6af4247888d + content/57: 374b1eb956e935cf3f51c46af62831ac + content/58: ad94b06eaa66c2fd263362b6af04b9c0 + content/59: c6906582420cbe603d1bb135d7cdc13a + content/60: e6d0829ea90222582df021491c834609 + content/61: c6b7171acaf9ea0be21b408e4beb6c73 + content/62: bbfc8b8ec96997b3e589802f18424196 + content/63: a57adc8d19410c1974f8d59a2e2e1cb9 + content/64: f83c5cc385540bbff0ce0280e31d703d + content/65: 8040541596306a73329cc1f4c375d616 + content/66: c32975398faa1ee29ba3cdfd5bd5e314 + content/67: 0a33871a9ed20bf823cb2c45dc0c91d1 + content/68: 8bff38b0eba94b9ea1b67e7472b74cfc + content/69: 3194ad8ddf9bbe2500b571a9417e2101 + content/70: bec0c223723733b84c83e6d13b637855 + content/71: 1c526016dbe08ff63980dc829cc327f7 + content/72: ce50bb578174216d8d0625907a242263 + content/73: 61f4238f95cc209302563fe0d66aea20 + content/74: 3cc855439498819a71e24a9d44678705 + content/75: dd256e45824b048778e81a0e80a3a6cf + content/76: b384ef84f794014c78bd7232f18d3b5f + content/77: 19f49f0312ddc4ded8f6737083673edf + content/78: 9572e0cb99536535c176db24863ce217 + content/79: 40a3888d076524edcb9716f64683c5b3 + content/80: 041645f33a12c870680e713dc7b8d78a + content/81: 98200505f691d3dcb12fa6833d0e88ad + content/82: 6fecb4eaca40bae0bb37fbe65ff6a637 + content/83: 4455b21dd9cea4b18d0d3991c010eacb + content/84: 61b6140fd37b3d64ac6cf6a5529119a8 + content/85: 539769066341e46b743c0cfda822f3f9 + content/86: c66f09fe4fa88a9e016ebe4f0a2ed9b0 + content/87: 9461907d8be2aa04f16b3d6cae972142 + content/88: 7f85a8c7e5217b02244281defb8e8592 + content/89: 46f1082308b9860120a46a27c8b14892 + content/90: 43e6ec093695c83476ddafd3674f8ec0 + content/91: 5425ae9feefbe5a5ce28290afc321625 + content/92: 853f6a32a56f50b2366cc2e9bfe30cef + content/93: 54dcc7d2d5e4c5ed2c9303950e89ab0d + content/94: aa1e5bc107219b8f73778df6a2eaf649 + content/95: 3898b08aad245d481c00759bba165827 + content/96: 5a26c709955575918c0b6c9093f47410 + content/97: cbb6dd927a2ba8dbe47777babba90548 + content/98: 080834714f41b28fa5e15cc6139d4346 + content/99: 2c595c5d764fd8e4bc386193ea202888 + content/100: 083e769853a4074246e3b140c55e2778 + content/101: a24eeda297d1c1e30583069309251316 + content/102: d860fb4c61d9774d524a4136c86cb598 + content/103: cc20ee2981f70eeb7e0fa675624ef43d + content/104: 29c9df71eb3d97cd840e59bfa6d65acc + content/105: 50c9b9a3be9b72db4c1dbc46fb96e835 + content/106: 8f85d64b4bb8cedd8d9ee8cc962ec075 + content/107: 79524f874744893afda00224a0203ea6 + content/108: 656598f8d7330171f0a3f1f73e884e9f + content/109: eb7a29fcbea23aeecc0192229112e275 + content/110: 25d2f401cdd79bd9c1e761535eb145ce + content/111: 1c813fba4719c7b29863b4944f3dc074 + content/112: 450ad7f99b79b12599856f813b9ef758 + content/113: 39f313714117fbf6ed7b64076ffe1498 + content/114: bb4a3e2c68e0f832f815b659210ef260 + content/115: aae63d8bf6b6da4ac6fb501e13691e4d + content/116: 7a451aee1061c1cf2a4108f13acbafff + content/117: 10e8e9a59847ddd720c6d06b12c7b120 + content/118: 9799b973234bf2d10d5c971b267f55a5 6a9739e5e2a2b2be16e4da3ab6f64867: meta/title: 230344f713c89383f51389be1e0d3f0d meta/description: 0a913183ce5ee8cfafa21bafe1b0a3e1 @@ -1620,78 +1621,79 @@ checksums: content/33: 6ee187c17478546ee6995ddc7ff71f7d content/34: fd28f573fd8a586b5367a4f9b28e3769 content/35: 5f8179f48cce17a9dfc61a6e5e25635c - content/36: b1e0b4a77be6bf4155907e2535c08ce8 + content/36: 44e62155a0426ebb1e2cfe3452859c1c content/37: 64e7d4b48377ae88bdb94c0526f3ac52 - content/38: 8585c7df50d3e4a85a4fdac34176f248 - content/39: 87ffdb6e1e2f3aa7f0988a50eed855ec + content/38: 00d6230eeeda9c646035bb68b08682f6 + content/39: 1389ca754bbb08581696ce740b2f7afa content/40: 5c144d24f704e1a240b89ab6df4148ba - content/41: 5715f85e92401fbe93574ae58e5cd5b7 + content/41: 5e38c4efb5d411c77e4b36260df89ce0 content/42: d67621c5d4d9fd33358a7ecec0338342 - content/43: 18b88784316174b560051890fe13efa0 + content/43: 218e40ee0c9c118800a6bbc8c22a0406 content/44: 73dc7193c431ee27face3e96cca59471 - content/45: 2da3bce39f011d227e7439bf39000c4b - content/46: 376db24492fd179249a97399cac62c7b - content/47: cbe733e8e67272ce1e1719f34c29ce16 - content/48: be8ee42c61c2f8412a2223346fd1e5d7 - content/49: 19ddbf4a6a27d1217a35ff1ab7082883 - content/50: 6978c15affabcb936aeac6d02d2a9201 - content/51: f53d2373b481a20fe76d332f4a102b84 - content/52: 26f14651033af5a7e0da93c5dd653916 - content/53: 84be083ce8f618a49aa37c1b5ebae0ad - content/54: 29eba1f6a44ec24ee2b0d97faff09564 - content/55: 7fc3a5a4f12e019d5546b6c8688751ce - content/56: 7034f8f1bcdb020342cc70f6ace334b7 - content/57: 610db0f043625ec6ace32150e8b96a66 - content/58: 094e89e6ac397f5e15ea0fc1c209558d - content/59: 20e3ac38d3cc61e817f1dffacaa1c21e - content/60: defd8dab42b430e9687cc7ca90e0052e - content/61: 4dec52ce04aa8849a8a60508baae30ae - content/62: 258c9459be6a0fc3f535b41a89922f50 - content/63: 830481b98e88ae69a3971df3aff5d250 - content/64: 538df8a5de4562fa88ae14bd586adc97 - content/65: f90fc6fdfdcb80d45d9e53b6591d7331 - content/66: b0655e68a1c5d9579fbcfdd75db04fce - content/67: 71d221bcbeb83b663ee8c0a0e4975b04 - content/68: a40d8cc248a02d4ce34ae16119b14bf8 - content/69: 4e5ddca85ed818337a2757ae6bc2dc64 - content/70: a27e8a0a72073744f925f2820a1b7684 - content/71: 73aea3ef20f5f9d1626fcc73fdea597d - content/72: a01a451f7aba1a80f0af7148df811dfc - content/73: 84a204a7da04ff738ebcc5f4d3a5ea35 - content/74: 3347863434fc4a0bd8f075b971428c95 - content/75: bb2ac20c712241859473f415c26a0a11 - content/76: e180f5d5909fef8296047e0782fe8a85 - content/77: 6cc6ea0c653430147746ed145777d8e2 - content/78: 214aa9e0d35236656ba1738c8be4c3f0 - content/79: 919b58f12a4f16d63b5a35d18fdf04bb - content/80: 2ca28a1219570c10ddeaf4cb1157bb6d - content/81: a6390089f6e92430098d3d97c610c17a - content/82: 3710272f77f1cbde0159b940d8826829 - content/83: ff0a8fe413f503e4af629f445746a3dd - content/84: 300bcdb15fbcadfde3db772fafe63524 - content/85: 3d93f05e4ce92565d044836c948352a9 - content/86: 0d08ff106c532b64677561ea2c3bad1f - content/87: 116df75dc256a5019b4809510f35677c - content/88: d6f6ef2bbe6d6ec5e267b33ba910ca7d - content/89: 0ab559de8fc24b6e6cca6175444cfe9a - content/90: fab414b91a7a42cd5e37a0c5ade9945a - content/91: ed80be5f381f63c9293cc5c4182c0632 - content/92: 2d9538daa0f3bcd2d760598097ee5d80 - content/93: 9c3265eff155d4a823fd955cde7dba35 - content/94: ff3d86520f4003c526e008912dbd57fd - content/95: ed10345c824c6fc4c69c882e9983b12b - content/96: fb9ce2e2edb7815efec73b696b5d5e95 - content/97: 25836d79f15e851139e61b1d31fd3c73 - content/98: 44d8170f535788a47c7874e25dedb768 - content/99: fa9f6f1b8a7829a8e68a1d0340a84533 - content/100: 857c1365962ba1b8c02e75f877a5b586 - content/101: 79524f874744893afda00224a0203ea6 - content/102: 0fa45599d835da6f2e916859554673eb - content/103: 450ad7f99b79b12599856f813b9ef758 - content/104: 4f9f49c4756aa59d40fcc76ca15669a9 - content/105: f151f31b2581a7bd7f7d8635bff09dc8 - content/106: 10e8e9a59847ddd720c6d06b12c7b120 - content/107: 9799b973234bf2d10d5c971b267f55a5 + content/45: abdc26659c4e48ac2a87a447c8069e21 + content/46: 2da3bce39f011d227e7439bf39000c4b + content/47: 376db24492fd179249a97399cac62c7b + content/48: cbe733e8e67272ce1e1719f34c29ce16 + content/49: be8ee42c61c2f8412a2223346fd1e5d7 + content/50: 19ddbf4a6a27d1217a35ff1ab7082883 + content/51: 6978c15affabcb936aeac6d02d2a9201 + content/52: f53d2373b481a20fe76d332f4a102b84 + content/53: 26f14651033af5a7e0da93c5dd653916 + content/54: 84be083ce8f618a49aa37c1b5ebae0ad + content/55: 29eba1f6a44ec24ee2b0d97faff09564 + content/56: 7fc3a5a4f12e019d5546b6c8688751ce + content/57: 7034f8f1bcdb020342cc70f6ace334b7 + content/58: 610db0f043625ec6ace32150e8b96a66 + content/59: 094e89e6ac397f5e15ea0fc1c209558d + content/60: 20e3ac38d3cc61e817f1dffacaa1c21e + content/61: defd8dab42b430e9687cc7ca90e0052e + content/62: 4dec52ce04aa8849a8a60508baae30ae + content/63: 258c9459be6a0fc3f535b41a89922f50 + content/64: 830481b98e88ae69a3971df3aff5d250 + content/65: 538df8a5de4562fa88ae14bd586adc97 + content/66: f90fc6fdfdcb80d45d9e53b6591d7331 + content/67: b0655e68a1c5d9579fbcfdd75db04fce + content/68: 71d221bcbeb83b663ee8c0a0e4975b04 + content/69: a40d8cc248a02d4ce34ae16119b14bf8 + content/70: 4e5ddca85ed818337a2757ae6bc2dc64 + content/71: a27e8a0a72073744f925f2820a1b7684 + content/72: 73aea3ef20f5f9d1626fcc73fdea597d + content/73: a01a451f7aba1a80f0af7148df811dfc + content/74: 84a204a7da04ff738ebcc5f4d3a5ea35 + content/75: 3347863434fc4a0bd8f075b971428c95 + content/76: bb2ac20c712241859473f415c26a0a11 + content/77: e180f5d5909fef8296047e0782fe8a85 + content/78: 6cc6ea0c653430147746ed145777d8e2 + content/79: 214aa9e0d35236656ba1738c8be4c3f0 + content/80: 919b58f12a4f16d63b5a35d18fdf04bb + content/81: 2ca28a1219570c10ddeaf4cb1157bb6d + content/82: a6390089f6e92430098d3d97c610c17a + content/83: 3710272f77f1cbde0159b940d8826829 + content/84: ff0a8fe413f503e4af629f445746a3dd + content/85: 300bcdb15fbcadfde3db772fafe63524 + content/86: 3d93f05e4ce92565d044836c948352a9 + content/87: 0d08ff106c532b64677561ea2c3bad1f + content/88: 116df75dc256a5019b4809510f35677c + content/89: d6f6ef2bbe6d6ec5e267b33ba910ca7d + content/90: 0ab559de8fc24b6e6cca6175444cfe9a + content/91: fab414b91a7a42cd5e37a0c5ade9945a + content/92: ed80be5f381f63c9293cc5c4182c0632 + content/93: 2d9538daa0f3bcd2d760598097ee5d80 + content/94: 9c3265eff155d4a823fd955cde7dba35 + content/95: ff3d86520f4003c526e008912dbd57fd + content/96: ed10345c824c6fc4c69c882e9983b12b + content/97: fb9ce2e2edb7815efec73b696b5d5e95 + content/98: 25836d79f15e851139e61b1d31fd3c73 + content/99: 44d8170f535788a47c7874e25dedb768 + content/100: fa9f6f1b8a7829a8e68a1d0340a84533 + content/101: 857c1365962ba1b8c02e75f877a5b586 + content/102: 79524f874744893afda00224a0203ea6 + content/103: 0fa45599d835da6f2e916859554673eb + content/104: 450ad7f99b79b12599856f813b9ef758 + content/105: 4f9f49c4756aa59d40fcc76ca15669a9 + content/106: f151f31b2581a7bd7f7d8635bff09dc8 + content/107: 10e8e9a59847ddd720c6d06b12c7b120 + content/108: 9799b973234bf2d10d5c971b267f55a5 977811daa4a70d47883b868820f2faf6: meta/title: 76712cf76855f695712cf05b994caebd meta/description: 2ff5fa3d67801d9c8ac8e3b3c12d930f @@ -1791,115 +1793,116 @@ checksums: content/42: 640d0b31f6faa54914d25c81f5dbf413 content/43: e466f1ff116db35bc5803115bf80d07f content/44: 5b97682d8de32b1cdf287450ea6589ac - content/45: 021b2cc2690448c1c628ab330384609b + content/45: 19b3d1278e686840d42ac104c3540fdd content/46: a75a0e13bfec5fcaf46f0eab0c4f0c46 - content/47: 640d0b31f6faa54914d25c81f5dbf413 - content/48: 97fb14f09ea66ad1636fbe714fc54ca9 - content/49: aebfce781560272a78a1d6777b43636e - content/50: 0bed4847273bc1b883857c1a2dc58e89 - content/51: 58e77d0bc7402a709dbf54c4586208da - content/52: 7d445112e8e8c53a3bb78f3e0e53902d - content/53: 7b0c9986d8c79303926b5293cdb615c7 - content/54: 1421905fe50ea83bf892ed3a36ead4eb - content/55: 4eee624283334d8490037e07c14cc9f2 - content/56: c48e258e50122f0eb0bc8e89d5ad653e - content/57: aa2a6f4894a5d9b0b351702724d1b62b - content/58: 7e45f5de94c34c9dedf5f4636295e4c8 - content/59: a1d30d251f782065bf113889d28034d3 - content/60: 30444d8cb60acb7f30d3c7828e85cd4d - content/61: a03b9429133989d89ee9af6e111d13cc - content/62: 48a7ad3f0849fd9e3f29194c3033294b - content/63: 8ac07d18b175b18a7cc30397cdfc6235 - content/64: 725a7d24863328500e04505306d3f530 - content/65: 6ba587262d22a5b86d7a8ad89a9cd7e6 - content/66: 5d1b2cc1499b5dcd168c5cee8a0eb263 - content/67: 318e0c3aac0a4c2b6ea68d6586af76ed - content/68: 9f481780c40177897b369188ac4e14d1 - content/69: 5b1f5840c84fc15b238be30621c7040d - content/70: 36724b1e1a7d8ede3c3473511e0962c8 - content/71: 7dbb6d2a32807f07f8d15f295d6029c1 - content/72: 94ae1c950a664c7d6a71f543b3590645 - content/73: 959f4ff4a6314dcecc014d943a286a78 - content/74: e1bc33e13fc0ea4ccae263700fb87972 - content/75: e2a8aa98d08edf0385e18206dfcf209c - content/76: 0e4b2f9ac55cbfc1f8478c16fe462461 - content/77: 78a7ec2ac9e6297a1b73b52737d2f703 - content/78: 31209e62b8be12e97e731873a4b5f1dd - content/79: 65a222bfff97f1c2e088bbf2a6e64a98 - content/80: ce50ee3c3c46578e46a5b654992a7184 - content/81: 609fc14a8f76e947f5e0910252a84601 - content/82: 0762a64c782ab06b2497cb1e9bcd040c - content/83: ca93dd4e4bfb89506d0606d5504bb698 - content/84: 70ae6f507d495c0e7f08381093844545 - content/85: faef3168052f65fcae43f4e282c3405a - content/86: 37f44002ca6faa7edbb155f8ff13afe2 - content/87: af0d661c840762599d5bacf04887a063 - content/88: a8e033c5125c9b6035601165bf457168 - content/89: cf2fad1b631722b00fe74c4bc83c1c8f - content/90: da795a2fd9f1b2a97a753e5c7d7a31e0 - content/91: 2899bbcc52d19fd5403c595171e4428a - content/92: 34384a0b1a14658974468de46ae841b9 - content/93: 416104e9a22d7c8eff48d9aaf3a295ce - content/94: 2e210c407617dce99236163adcda8eee - content/95: 3225628004c6c872589b85bb8f01e922 - content/96: b984c1ec400c5b64605c8183fb0de89f - content/97: 0e97ce08c4c8aa23cd7bcb8c178f0526 - content/98: 69bbcf5ddbf3bc375a1c1dc9c0c543b1 - content/99: f4d0a6a649c7cdcdbf44cb505164c246 - content/100: f2b75ff3cb9bb1773411fd9a23b40bfe - content/101: e1b9c5e40f0f7590b6915570836f2ffc - content/102: 840d57f470753b5f90ee96f9664d4488 - content/103: fcf33f9e0158d0c0decf9372420591da - content/104: 21f2e03a976c6fdae0916fc35d0ff6da - content/105: f8b588f1d5f5a10be96cfb20ad48aa8d - content/106: 7895b67ee472b33c29e7b724c12194c2 - content/107: 5b193f9127ffeb27969b0eb77a9430af - content/108: 1f5b6d2e4f9a47eb9158baea95706524 - content/109: 72caa179bccffa9b96f0f8ef2ca0f586 - content/110: e4dccd32b2c9369cd4d30cd844c2d966 - content/111: 3772786592e54266e3b48bacd9d4a56d - content/112: 4c6760fc03981e65675173e761fac54d - content/113: fa75620e72e992981302952a5272a563 - content/114: a6194c147b3f8335a6c2742b9acff4e8 - content/115: 6dfb66af57472bd58ab7dfd6ae55b200 - content/116: d240f8d624a0b76dcf59a349a264fd9b - content/117: d44a76086ce37ddad6e96b3196844107 - content/118: ebf2b3224105b1f684830e34b62f1f55 - content/119: ff1b5b1aa8de35e9afbe19582ab6e869 - content/120: 0bc8fb337ad65d9ebc44d9a1d7dd2ff7 - content/121: 380be3ed5af370f2d36a66f4c55f7f0b - content/122: 2d593b029e19daed6f10c69c0d7759b7 - content/123: 764efc71060244b99a8d638241c9fe0a - content/124: e476fbe8422157871dfcb38fed4179d4 - content/125: 1401fd9898629387a54a4cae02dbcac3 - content/126: 32b6ace25ebb18bed2e07f6a71418a2c - content/127: 480e351bc384141feaf60b1b7005f4ce - content/128: f68a81262dfbea11f6a416580b09b560 - content/129: f2b75ff3cb9bb1773411fd9a23b40bfe - content/130: 72edab923832c1f02354f3b2f28b2c8d - content/131: d670e3e3157fac23c1ae4700d6ed016f - content/132: bf2537ca4fd9c3ee102e68965d99085a - content/133: b5876238045c1b0545fdea9a02afbbed - content/134: acb4a44a6c5a7026b7759ac11a5190e3 - content/135: 868c4213406def92d42d63677c91b3af - content/136: f34a3b1c211eab573cd322b75ca9e588 - content/137: b56e3520e6a6b8cace90ed3394a0e2c0 - content/138: 2db0d84588851b6e374c5a8dba239ce1 - content/139: 245d2df14086f9568a5be76056cc2321 - content/140: c01496d55003667a5ef3aa6f50c74c0b - content/141: d281e696b74d8885dfe93a2b63542cc7 - content/142: 29199935714ad8c4fe4eba8b918317b5 - content/143: f33dc3e3b584d665eccdcd9c19b04e96 - content/144: 36168229df04c0a38c9672a050a6f09c - content/145: 85927f2ffd88a327ea9184dc8b0aadc4 - content/146: 863fd994606a91d7b336123d31a22598 - content/147: 1db961ec80d8fcf5552d00d88ff31c6d - content/148: 984038ab5d384ea5c02da28d8c2d8c38 - content/149: 34ed0a9a3c2a1fb0f375cf4ccd239c2c - content/150: 3b80a9f8cbfd10fc7d7225aff25c0017 - content/151: e0c44df6762ce3227246cfcc87aee7fb - content/152: 10e8e9a59847ddd720c6d06b12c7b120 - content/153: 9799b973234bf2d10d5c971b267f55a5 + content/47: 45abcb9347d57ea0303a8a90ba13034c + content/48: 640d0b31f6faa54914d25c81f5dbf413 + content/49: 97fb14f09ea66ad1636fbe714fc54ca9 + content/50: aebfce781560272a78a1d6777b43636e + content/51: 0bed4847273bc1b883857c1a2dc58e89 + content/52: 58e77d0bc7402a709dbf54c4586208da + content/53: 7d445112e8e8c53a3bb78f3e0e53902d + content/54: 7b0c9986d8c79303926b5293cdb615c7 + content/55: 1421905fe50ea83bf892ed3a36ead4eb + content/56: 4eee624283334d8490037e07c14cc9f2 + content/57: c48e258e50122f0eb0bc8e89d5ad653e + content/58: aa2a6f4894a5d9b0b351702724d1b62b + content/59: 7e45f5de94c34c9dedf5f4636295e4c8 + content/60: a1d30d251f782065bf113889d28034d3 + content/61: 30444d8cb60acb7f30d3c7828e85cd4d + content/62: a03b9429133989d89ee9af6e111d13cc + content/63: 48a7ad3f0849fd9e3f29194c3033294b + content/64: 8ac07d18b175b18a7cc30397cdfc6235 + content/65: 725a7d24863328500e04505306d3f530 + content/66: 6ba587262d22a5b86d7a8ad89a9cd7e6 + content/67: 5d1b2cc1499b5dcd168c5cee8a0eb263 + content/68: 318e0c3aac0a4c2b6ea68d6586af76ed + content/69: 9f481780c40177897b369188ac4e14d1 + content/70: 5b1f5840c84fc15b238be30621c7040d + content/71: 36724b1e1a7d8ede3c3473511e0962c8 + content/72: 7dbb6d2a32807f07f8d15f295d6029c1 + content/73: 94ae1c950a664c7d6a71f543b3590645 + content/74: 959f4ff4a6314dcecc014d943a286a78 + content/75: e1bc33e13fc0ea4ccae263700fb87972 + content/76: e2a8aa98d08edf0385e18206dfcf209c + content/77: 0e4b2f9ac55cbfc1f8478c16fe462461 + content/78: 78a7ec2ac9e6297a1b73b52737d2f703 + content/79: 31209e62b8be12e97e731873a4b5f1dd + content/80: 65a222bfff97f1c2e088bbf2a6e64a98 + content/81: ce50ee3c3c46578e46a5b654992a7184 + content/82: 609fc14a8f76e947f5e0910252a84601 + content/83: 0762a64c782ab06b2497cb1e9bcd040c + content/84: ca93dd4e4bfb89506d0606d5504bb698 + content/85: 70ae6f507d495c0e7f08381093844545 + content/86: faef3168052f65fcae43f4e282c3405a + content/87: 37f44002ca6faa7edbb155f8ff13afe2 + content/88: af0d661c840762599d5bacf04887a063 + content/89: a8e033c5125c9b6035601165bf457168 + content/90: cf2fad1b631722b00fe74c4bc83c1c8f + content/91: da795a2fd9f1b2a97a753e5c7d7a31e0 + content/92: 2899bbcc52d19fd5403c595171e4428a + content/93: 34384a0b1a14658974468de46ae841b9 + content/94: 416104e9a22d7c8eff48d9aaf3a295ce + content/95: 2e210c407617dce99236163adcda8eee + content/96: 3225628004c6c872589b85bb8f01e922 + content/97: b984c1ec400c5b64605c8183fb0de89f + content/98: 0e97ce08c4c8aa23cd7bcb8c178f0526 + content/99: 69bbcf5ddbf3bc375a1c1dc9c0c543b1 + content/100: f4d0a6a649c7cdcdbf44cb505164c246 + content/101: f2b75ff3cb9bb1773411fd9a23b40bfe + content/102: e1b9c5e40f0f7590b6915570836f2ffc + content/103: 840d57f470753b5f90ee96f9664d4488 + content/104: fcf33f9e0158d0c0decf9372420591da + content/105: 21f2e03a976c6fdae0916fc35d0ff6da + content/106: f8b588f1d5f5a10be96cfb20ad48aa8d + content/107: 7895b67ee472b33c29e7b724c12194c2 + content/108: 5b193f9127ffeb27969b0eb77a9430af + content/109: 1f5b6d2e4f9a47eb9158baea95706524 + content/110: 72caa179bccffa9b96f0f8ef2ca0f586 + content/111: e4dccd32b2c9369cd4d30cd844c2d966 + content/112: 3772786592e54266e3b48bacd9d4a56d + content/113: 4c6760fc03981e65675173e761fac54d + content/114: fa75620e72e992981302952a5272a563 + content/115: a6194c147b3f8335a6c2742b9acff4e8 + content/116: 6dfb66af57472bd58ab7dfd6ae55b200 + content/117: d240f8d624a0b76dcf59a349a264fd9b + content/118: d44a76086ce37ddad6e96b3196844107 + content/119: ebf2b3224105b1f684830e34b62f1f55 + content/120: ff1b5b1aa8de35e9afbe19582ab6e869 + content/121: 0bc8fb337ad65d9ebc44d9a1d7dd2ff7 + content/122: 380be3ed5af370f2d36a66f4c55f7f0b + content/123: 2d593b029e19daed6f10c69c0d7759b7 + content/124: 764efc71060244b99a8d638241c9fe0a + content/125: e476fbe8422157871dfcb38fed4179d4 + content/126: 1401fd9898629387a54a4cae02dbcac3 + content/127: 32b6ace25ebb18bed2e07f6a71418a2c + content/128: 480e351bc384141feaf60b1b7005f4ce + content/129: f68a81262dfbea11f6a416580b09b560 + content/130: f2b75ff3cb9bb1773411fd9a23b40bfe + content/131: 72edab923832c1f02354f3b2f28b2c8d + content/132: d670e3e3157fac23c1ae4700d6ed016f + content/133: bf2537ca4fd9c3ee102e68965d99085a + content/134: b5876238045c1b0545fdea9a02afbbed + content/135: acb4a44a6c5a7026b7759ac11a5190e3 + content/136: 868c4213406def92d42d63677c91b3af + content/137: f34a3b1c211eab573cd322b75ca9e588 + content/138: b56e3520e6a6b8cace90ed3394a0e2c0 + content/139: 2db0d84588851b6e374c5a8dba239ce1 + content/140: 245d2df14086f9568a5be76056cc2321 + content/141: c01496d55003667a5ef3aa6f50c74c0b + content/142: d281e696b74d8885dfe93a2b63542cc7 + content/143: 29199935714ad8c4fe4eba8b918317b5 + content/144: f33dc3e3b584d665eccdcd9c19b04e96 + content/145: 36168229df04c0a38c9672a050a6f09c + content/146: 85927f2ffd88a327ea9184dc8b0aadc4 + content/147: 863fd994606a91d7b336123d31a22598 + content/148: 1db961ec80d8fcf5552d00d88ff31c6d + content/149: 984038ab5d384ea5c02da28d8c2d8c38 + content/150: 34ed0a9a3c2a1fb0f375cf4ccd239c2c + content/151: 3b80a9f8cbfd10fc7d7225aff25c0017 + content/152: e0c44df6762ce3227246cfcc87aee7fb + content/153: 10e8e9a59847ddd720c6d06b12c7b120 + content/154: 9799b973234bf2d10d5c971b267f55a5 c25d26c5f6016f2f43987cd7d0b8cd6c: meta/title: e771f23130f9aab4f3e2e99acacf5d5a meta/description: 8539ebe90ff733bef6498db1933f3a57 From 32e6532c783012cbcaff1160eaaa4011be4b4be2 Mon Sep 17 00:00:00 2001 From: Charlie Hopkins-Brinicombe Date: Wed, 15 Apr 2026 23:03:01 +0100 Subject: [PATCH 7/7] Add option to force translation of specific files. Regenerate key translations due to lock drift --- README.md | 12 +- es/platform/achievements.mdx | 192 +++++++++++------------ es/platform/leaderboards.mdx | 157 +++++++++---------- es/platform/points.mdx | 245 +++++++++++++++--------------- scripts/generate-translations.mjs | 32 +++- 5 files changed, 342 insertions(+), 296 deletions(-) diff --git a/README.md b/README.md index e00077b..e323537 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Use `npm run