crt-ship 0.1.0: CLI, Claude Code skills and templates for shipping mobile games to Google Play and the App Store

This commit is contained in:
CRTkafa 2026-10-10 20:29:20 +08:00
commit 3cd31b7601
64 changed files with 4509 additions and 0 deletions

View file

@ -0,0 +1,15 @@
{
"name": "crtkafa",
"description": "CRTkafa's open-sourced tools.",
"owner": { "name": "CRTkafa" },
"plugins": [
{
"name": "crt-ship",
"source": ".",
"description": "Ship mobile games to Google Play and the App Store: skills for every store step plus the crt-ship CLI.",
"version": "0.1.0",
"category": "development",
"tags": ["mobile", "google-play", "app-store", "release"]
}
]
}

View file

@ -0,0 +1,10 @@
{
"name": "crt-ship",
"version": "0.1.0",
"description": "Ship mobile games to Google Play and the App Store: store listings, in-app products, data safety, screenshots, icons and first-release checklists, as skills plus a Bun CLI (crt-ship). CRTkafa's own toolchain, open-sourced.",
"author": { "name": "CRTkafa" },
"homepage": "https://git.crt.fyi/CRT/crt-ship",
"repository": "https://git.crt.fyi/CRT/crt-ship.git",
"license": "MIT",
"keywords": ["google-play", "app-store", "app-store-connect", "mobile-games", "iap", "screenshots", "app-icons", "release"]
}

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
node_modules/
.crt-ship/
*.p8
*service-account*.json
*.keystore
*.jks
*.p12
.DS_Store

15
CHANGELOG.md Normal file
View file

@ -0,0 +1,15 @@
# Changelog
## 0.1.0 · 2026-10-10
First public cut, extracted from the scripts I used to ship my own game.
- `crt-ship` CLI: `init`, `check`; Play `products`, `listing`, `images`, `data-safety`, `release`, `status`;
App Store `bundle`, `iap`, `iap-review`, `listing`, `screenshots`, `appinfo`, `review-details`, `status`
(incl. canceling a review submission); `icons`; `shots capture | fonts | render | jpeg`.
- Per-project `crt-ship.config.ts`; credentials only from env or `.crt-ship/credentials.json`.
- Claude Code plugin + marketplace with 8 skills: play-first-release, appstore-first-release, store-iap-setup,
store-screenshots, app-icons, store-declarations, privacy-policy, ads-levelplay-checks.
- Templates: config, Play + App Store listing copy, screenshot copy (5 languages incl. Arabic + Japanese), Data
safety CSV, App Privacy answers, Play declarations, studio-wide privacy policy page.
- Tests for config validation, copy parsing, Data safety CSV, price mapping, script fonts, args.

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 CRTkafa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

136
README.md Normal file
View file

@ -0,0 +1,136 @@
# crt-ship
Hey. This is my toolchain for shipping mobile games to Google Play and the App Store. I built it while getting my
own game through both stores, hit every wall on the way (draft apps, edits that die when you touch the Console,
the "first IAP must ship with a version" dance, data safety CSVs that silently need one more question...), and wrote
each fix down as code or as a checklist. It works for me, so I open-sourced it. Friends, use it too.
It is not a product. No dashboard, no account, no telemetry. A small Bun CLI that talks to the official store APIs,
plus Claude Code skills that walk you (or Claude) through the parts that are still clicks in a web console.
· CRTkafa
## What is in the box
- **`crt-ship` CLI** (Bun + TypeScript, zero runtime dependencies): store listings, in-app products with regional
prices, Play Data safety, AAB releases, App Store versions / screenshots / age rating / review details, icons from
one artwork, and a screenshot renderer (captioned scenes, device frames, 14+ languages incl. RTL and CJK).
- **Claude Code skills**: ordered steps for each job, which steps are API and which are UI-only (with the exact
console navigation), the gotchas, and how to verify.
- **Templates**: config, listing copy, screenshot copy, Data safety CSV, App Privacy answers, Play declarations,
a studio-wide privacy policy page.
## Install
As a Claude Code plugin (skills + the `crt-ship` command on Claude's PATH):
```
/plugin marketplace add https://git.crt.fyi/CRT/crt-ship.git
/plugin install crt-ship@crtkafa
```
As a CLI in your game repo (needs [Bun](https://bun.sh) 1.1+):
```sh
bun add -d git+https://git.crt.fyi/CRT/crt-ship.git
bunx crt-ship help
```
Extra tools some commands use: `ffmpeg` (icons, screenshots), `cwebp` (optional), `playwright` in your project for
screenshots (`bun add -d playwright`; it uses Playwright's Chromium or your installed Chrome).
## Quick start
```sh
bunx crt-ship init # crt-ship.config.ts, store/*.md, store/shots/copy.json, privacy policy, .crt-ship/ (gitignored)
# edit crt-ship.config.ts and the store/ files
bunx crt-ship check # offline: config, copy limits, data safety, files
bunx crt-ship play listing --dry
bunx crt-ship apple iap --dry
```
Or just ask Claude: "ship my game to Google Play" / "set up the in-app purchases on both stores" / "make store
screenshots in 5 languages", and the matching skill kicks in.
## Credentials (never in the repo)
Read from environment variables, or from `.crt-ship/credentials.json` in your project (gitignored by `init`):
| name | what |
|---|---|
| `PLAY_SERVICE_ACCOUNT` | path to the Google Cloud service account JSON key (or the JSON itself) with access to your app in Play Console |
| `ASC_KEY_ID`, `ASC_ISSUER_ID` | App Store Connect API team key (Users and Access > Integrations) |
| `ASC_P8` | path to `AuthKey_<id>.p8` (default `~/.appstoreconnect/private_keys/AuthKey_<id>.p8`) |
| `reviewContact` | (file only) App Review contact name / phone / email |
## Commands
Every write command takes `--dry`; re-runs are idempotent; output is one timestamped line per step, so long uploads
can run in the background with a log (`crt-ship play images > .crt-ship/images.log 2>&1 &`).
| command | API / local | what |
|---|---|---|
| `init` | local | copy the templates, gitignore `.crt-ship/` |
| `check` | local | validate config, copy limits, data safety, products, files |
| `play products` | API | one-time products via `monetization.onetimeproducts`: regional prices from `convertRegionPrices` + your overrides, activate |
| `play listing` | API | app details + title / short / full description per language |
| `play images` | API | icon, feature graphic, phone / 7" / 10" screenshots, one edit per language |
| `play data-safety` | API | Data safety form (CSV import); `--csv` to write the file |
| `play release <aab>` | API | upload + tracks (`--internal completed --production draft`), release notes from the listing |
| `play status` | API | tracks, listings, products |
| `apple bundle` | API | register the bundle id, find the app record |
| `apple iap` | API | IAPs, localizations, price schedules (USA base + overrides), availability |
| `apple iap-review` | API | IAP review screenshots + states |
| `apple listing --version x.y.z [--build N]` | API | version, build, name / subtitle / description / keywords / promo per locale |
| `apple screenshots` | API | 6.9" + 6.5" screenshots per locale (MD5-idempotent) |
| `apple appinfo` | API | categories, age rating, content rights, Free, all territories |
| `apple review-details` | API | App Review contact + notes |
| `apple status` | API | versions, builds, IAPs, review submissions (`--cancel-submission <id>`) |
| `icons` | local | App Store 1024 (no alpha), Play 512, web, Capacitor sources, Android legacy launcher icons |
| `shots capture` | local | raw frames from your running game |
| `shots fonts` | local | font subsets for your copy (Noto for CJK / Arabic / Cyrillic / Devanagari ...) |
| `shots render` | local | every store size, every language, contact sheet |
| `shots jpeg` | local | JPEG q92 copies for Play (about 4x faster uploads) |
## What is API and what stays manual
Scripted: almost everything listed above. Still clicks (the skills give the exact path):
- **Google Play**: creating the app, API access + service account invite, the first upload in some cases, App content
declarations (privacy policy, ads, IARC content rating, target audience, advertising ID, sign-in details,
government / financial / health), countries for production, and **Send for review** on a draft app's first release.
- **App Store**: creating the app record, App Privacy labels (+ Publish), TestFlight group setup, adding first IAPs to
the submission ("Add for Review" on the IAP page), and **Submit for Review**.
Public or irreversible steps are left to you on purpose. The skills tell Claude to ask before any of them.
## Skills
| skill | for |
|---|---|
| `play-first-release` | Google Play from zero to "In review" |
| `appstore-first-release` | App Store from API key to "Waiting for review", incl. xcodebuild with the API key |
| `store-iap-setup` | in-app products on both stores, prices, testers, the first-IAP-with-version rule |
| `store-screenshots` | capture, copy, fonts, render, upload |
| `app-icons` | one artwork to every icon, adaptive / legacy launcher pitfalls |
| `store-declarations` | Data safety, App Privacy, IARC, target audience, ads / advertising ID |
| `privacy-policy` | one studio-wide policy page with a per-game services table |
| `ads-levelplay-checks` | LevelPlay / Unity Ads ids, adapters, app-ads.txt, SKAdNetwork, no-fill |
## Develop
```sh
bun install
bun test # pure parts: config validation, copy parsing, CSV, prices, fonts, args
bun run typecheck
```
## Credits
Built with Bun and plain `fetch` against the Google Play Developer API v3 and the App Store Connect API. Screenshot
fonts come from Google Fonts at render time (Noto, Lilita One, Montserrat; SIL Open Font License 1.1). Not affiliated
with Google or Apple.
## License
MIT, see [LICENSE](LICENSE). Copyright (c) 2026 CRTkafa.

4
bin/crt-ship Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
# crt-ship launcher. On PATH while the Claude Code plugin is enabled; or symlink it anywhere.
here=$(cd "$(dirname "$0")" && pwd -P)
exec bun "$here/../cli/index.ts" "$@"

71
cli/apple/appinfo.ts Normal file
View file

@ -0,0 +1,71 @@
// App-level App Store settings (idempotent), from apple.info in crt-ship.config.ts:
// crt-ship apple appinfo [--dry]
// - categories (primary + up to two game subcategories, secondary)
// - age rating declaration: every content question NONE / false unless overridden in apple.info.ageRating
// - content rights declaration
// - price Free (USA base) and availability in every territory + new ones (when apple.info.free !== false)
// App Privacy ("nutrition label") has no API: web UI only (store-declarations skill).
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log } from '../lib/log';
import { ascClient } from './asc';
/** Every question of the current age rating declaration at its "nothing" answer. */
export const AGE_RATING_NONE: Record<string, string | boolean | null> = {
advertising: false, ageAssurance: false, alcoholTobaccoOrDrugUseOrReferences: 'NONE', contests: 'NONE', gambling: false,
gamblingSimulated: 'NONE', gunsOrOtherWeapons: 'NONE', healthOrWellnessTopics: false, horrorOrFearThemes: 'NONE', lootBox: false,
matureOrSuggestiveThemes: 'NONE', medicalOrTreatmentInformation: 'NONE', messagingAndChat: false, parentalControls: false,
profanityOrCrudeHumor: 'NONE', sexualContentGraphicAndNudity: 'NONE', sexualContentOrNudity: 'NONE', unrestrictedWebAccess: false,
userGeneratedContent: false, violenceCartoonOrFantasy: 'NONE', violenceRealistic: 'NONE',
violenceRealisticProlongedGraphicOrSadistic: 'NONE', kidsAgeBand: null,
};
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const info = config.apple?.info ?? {};
const dry = ctx.args.has('dry');
const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId);
const appId = await c.app();
const appInfo = await c.editableAppInfo();
const say = (m: string) => log('apple', dry ? `${m} (dry)` : m);
if (info.categories) {
const k = info.categories;
const rel = (id?: string) => ({ data: id ? { type: 'appCategories', id } : null });
if (!dry) await c.asc('PATCH', `/v1/appInfos/${appInfo.id}`, { data: { type: 'appInfos', id: appInfo.id, relationships: {
primaryCategory: rel(k.primary), primarySubcategoryOne: rel(k.primarySubOne), primarySubcategoryTwo: rel(k.primarySubTwo), secondaryCategory: rel(k.secondary) } } });
say(`categories: ${[k.primary, k.primarySubOne, k.primarySubTwo].filter(Boolean).join(' / ')}${k.secondary ? `, secondary ${k.secondary}` : ''}`);
}
const attributes = { ...AGE_RATING_NONE, ...(info.ageRating ?? {}) };
if (!dry) await c.asc('PATCH', `/v1/ageRatingDeclarations/${appInfo.id}`, { data: { type: 'ageRatingDeclarations', id: appInfo.id, attributes } });
say(`age rating declaration: ${Object.entries(info.ageRating ?? {}).map(([q, v]) => `${q}=${v}`).join(', ') || 'all NONE'}`);
if (info.contentRights) {
if (!dry) await c.asc('PATCH', `/v1/apps/${appId}`, { data: { type: 'apps', id: appId, attributes: { contentRightsDeclaration: info.contentRights } } });
say(`content rights: ${info.contentRights}`);
}
if (info.free !== false) {
const sched = await c.maybe(`/v1/apps/${appId}/appPriceSchedule?include=manualPrices`);
if (!sched?.included?.some((x: { type: string }) => x.type === 'appPrices')) {
const free = (await c.all(`/v1/apps/${appId}/appPricePoints?filter[territory]=USA&limit=200`)).find(p => Number(p.attributes.customerPrice) === 0);
if (!free) throw new Error('no free price point for USA');
if (!dry) await c.asc('POST', '/v1/appPriceSchedules', { data: { type: 'appPriceSchedules', relationships: {
app: { data: { type: 'apps', id: appId } }, baseTerritory: { data: { type: 'territories', id: 'USA' } },
manualPrices: { data: [{ type: 'appPrices', id: '${free}' }] } } },
included: [{ type: 'appPrices', id: '${free}', attributes: { startDate: null }, relationships: { appPricePoint: { data: { type: 'appPricePoints', id: free.id } } } }] });
say('price: Free (USA base)');
} else log('apple', 'price schedule exists');
const avail = await c.maybe(`/v1/apps/${appId}/appAvailabilityV2`);
if (!avail?.data) {
const ts: string[] = (await c.all('/v1/territories?limit=200')).map(t => t.id);
if (!dry) await c.asc('POST', '/v2/appAvailabilities', { data: { type: 'appAvailabilities', attributes: { availableInNewTerritories: true },
relationships: { app: { data: { type: 'apps', id: appId } }, territoryAvailabilities: { data: ts.map(t => ({ type: 'territoryAvailabilities', id: '${' + t + '}' })) } } },
included: ts.map(t => ({ type: 'territoryAvailabilities', id: '${' + t + '}', attributes: { available: true }, relationships: { territory: { data: { type: 'territories', id: t } } } })) });
say(`availability: ${ts.length} territories + new ones`);
} else log('apple', 'availability exists');
}
done('apple', 'appinfo done (App Privacy is UI-only: store-declarations skill)');
}

89
cli/apple/asc.ts Normal file
View file

@ -0,0 +1,89 @@
// App Store Connect API client: ES256 JWT from a team API key (Users and Access > Integrations > App Store Connect
// API; role Admin or App Manager), paging, retries, the asset upload protocol, and the app / version lookups.
import { createHash, createPrivateKey, sign } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { ascKey } from '../lib/credentials';
const b64 = (b: Buffer | string): string => Buffer.from(b).toString('base64url');
export const md5 = (b: Buffer | Uint8Array): string => createHash('md5').update(b).digest('hex');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type J = any;
export type Asc = Awaited<ReturnType<typeof ascClient>>;
/** Versions that can still be edited (being prepared or sent back). */
export const EDITABLE = new Set(['PREPARE_FOR_SUBMISSION', 'DEVELOPER_REJECTED', 'REJECTED', 'METADATA_REJECTED', 'INVALID_BINARY']);
export async function ascClient(root: string, bundleId: string, appIdHint?: string) {
const k = ascKey(root);
const key = createPrivateKey(k.pem);
let cached = { jwt: '', until: 0 };
function jwt(): string {
const now = Math.floor(Date.now() / 1000);
if (cached.jwt && now < cached.until) return cached.jwt;
const h = b64(JSON.stringify({ alg: 'ES256', kid: k.keyId, typ: 'JWT' }));
const p = b64(JSON.stringify({ iss: k.issuerId, iat: now, exp: now + 1100, aud: 'appstoreconnect-v1' }));
cached = { jwt: `${h}.${p}.${b64(sign('sha256', Buffer.from(h + '.' + p), { key, dsaEncoding: 'ieee-p1363' }))}`, until: now + 900 };
return cached.jwt;
}
async function asc(method: string, path: string, body?: unknown, attempt = 1): Promise<J> {
const r = await fetch(path.startsWith('http') ? path : 'https://api.appstoreconnect.apple.com' + path, {
method, headers: { Authorization: 'Bearer ' + jwt(), 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined,
});
const t = await r.text();
if ((r.status >= 500 || r.status === 429) && attempt < 5) { await Bun.sleep(2000 * attempt); return asc(method, path, body, attempt + 1); }
if (!r.ok) throw new Error(`${method} ${path} -> ${r.status} ${t.slice(0, 1500)}`);
return t ? JSON.parse(t) : {};
}
/** Every item of a paged GET. */
async function all(path: string): Promise<J[]> {
const out: J[] = [];
let url: string | null = path;
while (url) { const page: J = await asc('GET', url); out.push(...(page.data ?? [])); url = page.links?.next ?? null; }
return out;
}
/** null on 404 (relationship not created yet). */
async function maybe(path: string): Promise<J | null> {
try { return await asc('GET', path); } catch (e) { if (/-> 404/.test(String(e))) return null; throw e; }
}
let appId = appIdHint ?? '';
async function app(): Promise<string> {
if (appId) return appId;
const found = await asc('GET', `/v1/apps?filter[bundleId]=${encodeURIComponent(bundleId)}`);
const hit = (found.data as J[]).find(a => a.attributes.bundleId === bundleId);
if (!hit) throw new Error(`no App Store Connect app with bundle id ${bundleId}: create the app record in the web UI first (Apps > + > New App); see the appstore-first-release skill`);
appId = hit.id;
return appId;
}
async function editableVersion(): Promise<J | null> {
const vs = await all(`/v1/apps/${await app()}/appStoreVersions?filter[platform]=IOS&limit=50`);
return vs.find(v => EDITABLE.has(v.attributes.appVersionState ?? v.attributes.appStoreState)) ?? null;
}
async function editableAppInfo(): Promise<J> {
const infos = await all(`/v1/apps/${await app()}/appInfos`);
return infos.find(i => !['READY_FOR_DISTRIBUTION', 'REPLACED_WITH_NEW_INFO'].includes(i.attributes.state ?? i.attributes.appStoreState)) ?? infos[0];
}
/** Reserve -> PUT the parts -> commit with the MD5 (screenshots, IAP review screenshots). */
async function uploadAsset(type: string, rel: Record<string, unknown>, file: string): Promise<string> {
const buf = readFileSync(file);
const name = file.split('/').pop()!;
const res = (await asc('POST', `/v1/${type}`, { data: { type, attributes: { fileName: name, fileSize: buf.length }, relationships: rel } })).data;
for (const op of res.attributes.uploadOperations as J[]) {
const headers: Record<string, string> = {};
for (const h of op.requestHeaders) headers[h.name] = h.value;
const r = await fetch(op.url, { method: op.method, headers, body: buf.subarray(op.offset, op.offset + op.length) });
if (!r.ok) throw new Error(`upload ${name} part ${op.offset} -> ${r.status}`);
}
await asc('PATCH', `/v1/${type}/${res.id}`, { data: { type, id: res.id, attributes: { uploaded: true, sourceFileChecksum: md5(buf) } } });
return res.id;
}
return { asc, all, maybe, app, editableVersion, editableAppInfo, uploadAsset, keyId: k.keyId };
}

26
cli/apple/bundle.ts Normal file
View file

@ -0,0 +1,26 @@
// Register the bundle id (Certificates, Identifiers & Profiles) through the API. Idempotent.
// crt-ship apple bundle [--name "My Game"] [--dry]
// The App Store Connect app record itself can NOT be created through the API: do that in the web UI afterwards
// (Apps > + > New App, pick this bundle id), see the appstore-first-release skill.
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log } from '../lib/log';
import { ascClient } from './asc';
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const bundleId = need(config.apple?.bundleId, 'apple.bundleId');
const c = await ascClient(root, bundleId, config.apple?.appId);
const found = (await c.asc('GET', `/v1/bundleIds?filter[identifier]=${encodeURIComponent(bundleId)}`)).data
.filter((b: { attributes: { identifier: string } }) => b.attributes.identifier === bundleId);
if (found.length) { log('apple', `bundle id ${bundleId} already registered (${found[0].attributes.platform})`); }
else if (ctx.args.has('dry')) { log('apple', `bundle id ${bundleId} missing (dry run, not registering)`); return; }
else {
await c.asc('POST', '/v1/bundleIds', { data: { type: 'bundleIds', attributes: { identifier: bundleId, name: ctx.args.str('name', config.app.name).replace(/[^A-Za-z0-9 ]/g, ' ').trim(), platform: 'IOS' } } });
log('apple', `bundle id ${bundleId} registered`);
}
const apps = (await c.asc('GET', `/v1/apps?filter[bundleId]=${encodeURIComponent(bundleId)}`)).data;
if (apps.length) log('apple', `app record: ${apps[0].attributes.name} (Apple ID ${apps[0].id}); put it in apple.appId`);
else log('apple', 'no app record yet: App Store Connect > Apps > + > New App (UI only), then re-run `apple status`');
done('apple', 'bundle done');
}

35
cli/apple/iap-review.ts Normal file
View file

@ -0,0 +1,35 @@
// Upload the App Review screenshot of every in-app purchase (idempotent: a screenshot whose MD5 matches is kept) and
// print each product's state. Nothing is submitted.
// crt-ship apple iap-review [--dry]
// Screenshot per product: products[].reviewScreenshot, else apple.reviewShots/<product id>.(png|jpg).
// Size: a real device screenshot size (e.g. 1290 x 2796); other sizes fail with IMAGE_INCORRECT_DIMENSIONS.
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log, warn } from '../lib/log';
import { ascClient, md5 } from './asc';
export async function run(ctx: Ctx): Promise<void> {
const { config, root, resolve } = ctx.loaded;
const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId);
const appId = await c.app();
const defs = new Map((config.products ?? []).map(p => [p.id, p]));
for (const p of await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`)) {
const sku: string = p.attributes.productId;
const def = defs.get(sku);
const file = def?.reviewScreenshot ? resolve(def.reviewScreenshot)
: ['png', 'jpg', 'jpeg'].map(e => resolve(join(config.apple?.reviewShots ?? 'store/iap-review', `${sku}.${e}`))).find(existsSync);
if (!file || !existsSync(file)) { warn('apple', `${sku}: no review screenshot (set products[].reviewScreenshot)`); continue; }
const buf = readFileSync(file);
const cur = (await c.maybe(`/v2/inAppPurchases/${p.id}/appStoreReviewScreenshot`))?.data;
if (cur && cur.attributes.sourceFileChecksum === md5(buf) && cur.attributes.assetDeliveryState?.state !== 'FAILED') { log('apple', `${sku}: screenshot up to date`); continue; }
if (ctx.args.has('dry')) { log('apple', `${sku}: would upload ${file}`); continue; }
if (cur) await c.asc('DELETE', `/v1/inAppPurchaseAppStoreReviewScreenshots/${cur.id}`);
await c.uploadAsset('inAppPurchaseAppStoreReviewScreenshots', { inAppPurchaseV2: { data: { type: 'inAppPurchases', id: p.id } } }, file);
log('apple', `${sku}: screenshot uploaded`);
}
await Bun.sleep(3000);
for (const p of await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`)) log('apple', `${p.attributes.productId}: ${p.attributes.state}`);
done('apple', 'iap-review done (READY_TO_SUBMIT = complete; first IAPs go to review with a version, from the IAP page)');
}

81
cli/apple/iap.ts Normal file
View file

@ -0,0 +1,81 @@
// In-app purchases on App Store Connect: create missing products, localizations, price schedule (USA base + your
// overrides; Apple equalizes every other storefront) and availability (every territory + new ones). Idempotent:
// existing products and schedules are left alone (prices of an existing schedule are not changed; do that in the UI).
// crt-ship apple iap [--dry] [--only id1,id2]
// Then `apple iap-review` for the review screenshots. First IAPs can only be submitted together with an app version,
// from the IAP page in the web UI (see the store-iap-setup skill).
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log, step } from '../lib/log';
import { appleTerritory, nearestPrices, samePrice } from '../lib/money';
import { ascClient, type J } from './asc';
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const bundleId = need(config.apple?.bundleId, 'apple.bundleId');
const only = ctx.args.list('only');
const products = need(config.products, 'products').filter(p => !only.length || only.includes(p.id));
const dry = ctx.args.has('dry');
const c = await ascClient(root, bundleId, config.apple?.appId);
const appId = await c.app();
const langs = new Map(config.languages.map(l => [l.id, l]));
const territories: string[] = (await c.all('/v1/territories?limit=200')).map(t => t.id);
async function pricePoint(iapId: string, territory: string, price: number): Promise<string> {
const seen: number[] = [];
for (const p of await c.all(`/v2/inAppPurchases/${iapId}/pricePoints?filter[territory]=${territory}&limit=200`)) {
if (samePrice(p.attributes.customerPrice, price)) return p.id;
seen.push(Number(p.attributes.customerPrice));
}
throw new Error(`${territory} has no ${price} price point (nearest ${nearestPrices(seen, price).join(', ')})`);
}
for (const [i, p] of products.entries()) {
const tag = step(i + 1, products.length, p.id);
const found = await c.asc('GET', `/v1/apps/${appId}/inAppPurchasesV2?filter[productId]=${encodeURIComponent(p.id)}`);
let id: string = found.data[0]?.id;
if (dry) { log('apple', `${tag}: ${id ? `exists (${found.data[0].attributes.state})` : 'missing'} (dry)`); continue; }
if (!id) {
const first = Object.values(p.listings)[0]!;
id = (await c.asc('POST', '/v2/inAppPurchases', { data: { type: 'inAppPurchases',
attributes: { name: p.referenceName ?? first.title, productId: p.id, inAppPurchaseType: p.type === 'consumable' ? 'CONSUMABLE' : 'NON_CONSUMABLE',
familySharable: p.familySharable ?? false, ...(p.reviewNote ? { reviewNote: p.reviewNote } : {}) },
relationships: { app: { data: { type: 'apps', id: appId } } } } })).data.id;
log('apple', `${tag}: created`);
} else log('apple', `${tag}: exists`);
const have = new Set((await c.all(`/v2/inAppPurchases/${id}/inAppPurchaseLocalizations`)).map((l: J) => l.attributes.locale));
for (const [langId, l] of Object.entries(p.listings)) {
const locale = langs.get(langId)?.apple;
if (!locale || have.has(locale)) continue;
if ([...l.description].length > 55) throw new Error(`${p.id} ${locale}: description over 55 characters (Apple limit)`);
await c.asc('POST', '/v1/inAppPurchaseLocalizations', { data: { type: 'inAppPurchaseLocalizations', attributes: { locale, name: l.title, description: l.description },
relationships: { inAppPurchaseV2: { data: { type: 'inAppPurchases', id } } } } });
log('apple', ` localization ${locale}`);
}
const sched = await c.maybe(`/v2/inAppPurchases/${id}/iapPriceSchedule?include=manualPrices`);
if (!sched?.included?.length) {
const manual = [{ territory: 'USA', price: p.usd }, ...(p.overrides ?? []).map(o => ({ territory: appleTerritory(o), price: o.price }))];
const points = await Promise.all(manual.map(m => pricePoint(id, m.territory, m.price)));
await c.asc('POST', '/v1/inAppPurchasePriceSchedules', {
data: { type: 'inAppPurchasePriceSchedules', relationships: {
inAppPurchase: { data: { type: 'inAppPurchases', id } },
baseTerritory: { data: { type: 'territories', id: 'USA' } },
manualPrices: { data: manual.map(m => ({ type: 'inAppPurchasePrices', id: '${' + m.territory + '}' })) } } },
included: manual.map((m, k) => ({ type: 'inAppPurchasePrices', id: '${' + m.territory + '}', attributes: { startDate: null },
relationships: { inAppPurchasePricePoint: { data: { type: 'inAppPurchasePricePoints', id: points[k] } } } })),
});
log('apple', ` prices ${manual.map(m => `${m.territory} ${m.price}`).join(', ')}`);
}
const avail = await c.maybe(`/v2/inAppPurchases/${id}/inAppPurchaseAvailability`);
if (!avail?.data) {
await c.asc('POST', '/v1/inAppPurchaseAvailabilities', { data: { type: 'inAppPurchaseAvailabilities', attributes: { availableInNewTerritories: true },
relationships: { inAppPurchase: { data: { type: 'inAppPurchases', id } },
availableTerritories: { data: territories.map(t => ({ type: 'territories', id: t })) } } } });
log('apple', ` available in ${territories.length} territories + new ones`);
}
}
done('apple', dry ? 'iap checked (dry run)' : 'iap done (next: apple iap-review for the review screenshots)');
}

77
cli/apple/listing.ts Normal file
View file

@ -0,0 +1,77 @@
// App Store version + listing text (idempotent; re-run after editing the App Store listing file).
// crt-ship apple listing --version 1.0.0 [--build 3] [--dry]
// - the editable iOS version: versionString, copyright, release type MANUAL; with --build that processed build is attached
// - appInfoLocalizations: name, subtitle, privacy policy URL per locale
// - appStoreVersionLocalizations: description, keywords, promotional text, what's new (not on a first version),
// support + marketing URL per locale
import { readFileSync } from 'node:fs';
import { parseAppStoreListing } from '../lib/copy';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log } from '../lib/log';
import { ascClient, type J } from './asc';
export function readAppleCopy(ctx: Ctx) {
const { config, resolve } = ctx.loaded;
const only = ctx.args.list('only');
const all = parseAppStoreListing(readFileSync(resolve(config.apple?.listing ?? 'store/appstore.md'), 'utf8'));
return Object.fromEntries(Object.entries(all).filter(([l]) => !only.length || only.includes(l)));
}
async function upsertLocs(c: Awaited<ReturnType<typeof ascClient>>, list: string, type: string, parentRel: string, parentType: string, parentId: string,
want: Record<string, Record<string, string | undefined>>): Promise<void> {
const have = new Map((await c.all(`${list}?limit=200`)).map((l: J) => [l.attributes.locale, l] as const));
for (const [locale, raw] of Object.entries(want)) {
const attributes = Object.fromEntries(Object.entries(raw).filter(([, v]) => v !== undefined));
const cur = have.get(locale);
if (!cur) {
await c.asc('POST', `/v1/${type}`, { data: { type, attributes: { locale, ...attributes }, relationships: { [parentRel]: { data: { type: parentType, id: parentId } } } } });
log('apple', `${type} ${locale}: created`);
} else if (Object.entries(attributes).some(([k, v]) => cur.attributes[k] !== v)) {
await c.asc('PATCH', `/v1/${type}/${cur.id}`, { data: { type, id: cur.id, attributes } });
log('apple', `${type} ${locale}: updated`);
} else log('apple', `${type} ${locale}: up to date`);
}
}
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const a = ctx.args;
const version = a.get('version');
if (!version) throw new Error('usage: apple listing --version 1.0.0 [--build N] [--dry]');
const listing = readAppleCopy(ctx);
log('apple', `listing: ${Object.keys(listing).length} locales (${Object.keys(listing).join(', ')})`);
if (a.has('dry')) return;
const app = config.app;
const copyright = app.copyright ?? `${new Date().getFullYear()} ${app.name}`;
const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId);
const appId = await c.app();
let v = await c.editableVersion();
if (!v) {
v = (await c.asc('POST', '/v1/appStoreVersions', { data: { type: 'appStoreVersions',
attributes: { platform: 'IOS', versionString: version, copyright, releaseType: 'MANUAL' },
relationships: { app: { data: { type: 'apps', id: appId } } } } })).data;
log('apple', `version ${version} created`);
} else if (v.attributes.versionString !== version || v.attributes.copyright !== copyright || v.attributes.releaseType !== 'MANUAL') {
await c.asc('PATCH', `/v1/appStoreVersions/${v.id}`, { data: { type: 'appStoreVersions', id: v.id, attributes: { versionString: version, copyright, releaseType: 'MANUAL' } } });
log('apple', `version ${v.attributes.versionString} -> ${version}, copyright, release MANUAL`);
}
const build = a.get('build');
if (build) {
const b = (await c.all(`/v1/builds?filter[app]=${appId}&filter[version]=${build}&filter[preReleaseVersion.version]=${version}&filter[preReleaseVersion.platform]=IOS`))[0];
if (!b) throw new Error(`build ${version} (${build}) not found (still uploading? check TestFlight)`);
if (b.attributes.processingState !== 'VALID') throw new Error(`build ${build} is ${b.attributes.processingState}; wait until VALID`);
await c.asc('PATCH', `/v1/appStoreVersions/${v.id}/relationships/build`, { data: { type: 'builds', id: b.id } });
log('apple', `build ${version} (${build}) attached`);
}
const info = await c.editableAppInfo();
await upsertLocs(c, `/v1/appInfos/${info.id}/appInfoLocalizations`, 'appInfoLocalizations', 'appInfo', 'appInfos', info.id,
Object.fromEntries(Object.entries(listing).map(([l, f]) => [l, { name: f.name ?? app.name, subtitle: f.subtitle, privacyPolicyUrl: app.privacyPolicyUrl }])));
await upsertLocs(c, `/v1/appStoreVersions/${v.id}/appStoreVersionLocalizations`, 'appStoreVersionLocalizations', 'appStoreVersion', 'appStoreVersions', v.id,
Object.fromEntries(Object.entries(listing).map(([l, f]) => [l, { description: f.description, keywords: f.keywords, promotionalText: f.promotionalText,
whatsNew: f.whatsNew, supportUrl: app.supportUrl, marketingUrl: app.marketingUrl ?? app.website }])));
done('apple', 'listing done');
}

View file

@ -0,0 +1,39 @@
// App Review information on the editable version: contact, demo account (none), notes. Idempotent.
// crt-ship apple review-details [--contact-from <Apple ID of another app>] [--dry]
// The contact (name, phone, email) is personal data, so it is never in the config: it comes from
// .crt-ship/credentials.json "reviewContact", or is copied from another app of the same team with --contact-from.
// Notes: apple.info.reviewNotes in crt-ship.config.ts.
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { reviewContact } from '../lib/credentials';
import { done, log } from '../lib/log';
import { ascClient } from './asc';
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId);
const version = await c.editableVersion();
if (!version) throw new Error('no editable App Store version (apple listing --version x.y.z creates it)');
let contact: Record<string, string> | null = null;
const from = ctx.args.get('contact-from');
if (from) {
for (const v of await c.all(`/v1/apps/${from}/appStoreVersions?limit=10`)) {
const a = (await c.maybe(`/v1/appStoreVersions/${v.id}/appStoreReviewDetail`))?.data?.attributes;
if (a?.contactEmail) { contact = { contactFirstName: a.contactFirstName, contactLastName: a.contactLastName, contactPhone: a.contactPhone, contactEmail: a.contactEmail }; break; }
}
if (!contact) throw new Error(`app ${from} has no review contact on its recent versions`);
} else {
const rc = reviewContact(root);
if (rc) contact = { contactFirstName: rc.firstName, contactLastName: rc.lastName, contactPhone: rc.phone, contactEmail: rc.email };
}
if (!contact) throw new Error('no review contact: add "reviewContact" to .crt-ship/credentials.json or pass --contact-from <app id>');
const attributes = { ...contact, demoAccountRequired: false, demoAccountName: null, demoAccountPassword: null, notes: config.apple?.info?.reviewNotes ?? '' };
if (ctx.args.has('dry')) { log('apple', `would set review details (contact ${from ? 'copied from ' + from : 'from credentials'}, notes ${attributes.notes.length} chars)`); return; }
const cur = (await c.maybe(`/v1/appStoreVersions/${version.id}/appStoreReviewDetail`))?.data;
if (cur) await c.asc('PATCH', `/v1/appStoreReviewDetails/${cur.id}`, { data: { type: 'appStoreReviewDetails', id: cur.id, attributes } });
else await c.asc('POST', '/v1/appStoreReviewDetails', { data: { type: 'appStoreReviewDetails', attributes,
relationships: { appStoreVersion: { data: { type: 'appStoreVersions', id: version.id } } } } });
done('apple', `review details set on ${version.attributes.versionString} (no demo account, notes ${attributes.notes.length} chars)`);
}

70
cli/apple/screenshots.ts Normal file
View file

@ -0,0 +1,70 @@
// Store screenshots of every locale on the editable App Store version (idempotent: a set whose files already match
// by MD5 and order is left alone, otherwise it is emptied and re-uploaded).
// crt-ship apple screenshots [--src store/renders] [--only en-US,tr] [--jobs 4]
// Source per language: <renders>/<language id>/ios69/NN.png (6.9", 1320 x 2868 -> APP_IPHONE_67) and
// ios65/NN.png (6.5", 1284 x 2778 -> APP_IPHONE_65). Up to 10 per set. Apple processes uploads asynchronously; failed
// ones are reported at the end. Long: run in the background with a log file.
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import type { Ctx } from '../lib/ctx';
import { byAppleLocale, need } from '../lib/ctx';
import { done, log, warn } from '../lib/log';
import { numberedImages } from '../play/images';
import { ascClient, md5, type J } from './asc';
export const DISPLAY: Record<string, string> = { ios69: 'APP_IPHONE_67', ios65: 'APP_IPHONE_65' };
export async function run(ctx: Ctx): Promise<void> {
const { config, root, resolve } = ctx.loaded;
const src = resolve(ctx.args.get('src') ?? config.renders ?? config.shots?.out ?? 'store/renders');
const only = ctx.args.list('only');
const jobs = ctx.args.num('jobs', 4);
const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId);
const version = await c.editableVersion();
if (!version) throw new Error('no editable App Store version (run apple listing --version x.y.z first)');
const langOf = byAppleLocale(config.languages);
const locs = await c.all(`/v1/appStoreVersions/${version.id}/appStoreVersionLocalizations?limit=50`);
async function syncSet(locId: string, sets: J[], type: string, files: string[], tag: string): Promise<void> {
let set = sets.find(s => s.attributes.screenshotDisplayType === type);
if (!set) set = (await c.asc('POST', '/v1/appScreenshotSets', { data: { type: 'appScreenshotSets', attributes: { screenshotDisplayType: type },
relationships: { appStoreVersionLocalization: { data: { type: 'appStoreVersionLocalizations', id: locId } } } } })).data;
const existing = await c.all(`/v1/appScreenshotSets/${set.id}/appScreenshots`);
const want = files.map(f => md5(readFileSync(f)));
const have = existing.map(s => s.attributes.sourceFileChecksum);
const ok = existing.every(s => ['COMPLETE', 'UPLOAD_COMPLETE'].includes(s.attributes.assetDeliveryState?.state));
if (ok && have.length === want.length && have.every((h, i) => h === want[i])) { log('apple', `${tag}: ${files.length} up to date`); return; }
if (ctx.args.has('dry')) { log('apple', `${tag}: would replace ${existing.length} with ${files.length}`); return; }
for (const s of existing) await c.asc('DELETE', `/v1/appScreenshots/${s.id}`);
for (const f of files) await c.uploadAsset('appScreenshots', { appScreenshotSet: { data: { type: 'appScreenshotSets', id: set.id } } }, f);
log('apple', `${tag}: uploaded ${files.length} (removed ${existing.length})`);
}
const tasks: (() => Promise<void>)[] = [];
for (const loc of locs) {
const locale: string = loc.attributes.locale;
if (only.length && !only.includes(locale)) continue;
const lang = langOf.get(locale);
if (!lang) { warn('apple', `${locale}: no language with apple: "${locale}", skipped`); continue; }
tasks.push(async () => {
const sets = await c.all(`/v1/appStoreVersionLocalizations/${loc.id}/appScreenshotSets`);
for (const [dir, type] of Object.entries(DISPLAY)) {
const d = join(src, lang.id, dir);
const files = numberedImages(d).filter(f => f.endsWith('.png')).slice(0, 10);
if (!existsSync(d) || !files.length) { warn('apple', `${locale}: nothing in ${d}, skipped`); continue; }
await syncSet(loc.id, sets, type, files, `${locale} ${type}`);
}
});
}
log('apple', `${tasks.length} locales, ${jobs} in parallel`);
let next = 0;
await Promise.all(Array.from({ length: jobs }, async () => { while (next < tasks.length) await tasks[next++]!(); }));
let bad = 0;
for (const loc of locs) for (const set of await c.all(`/v1/appStoreVersionLocalizations/${loc.id}/appScreenshotSets`))
for (const s of await c.all(`/v1/appScreenshotSets/${set.id}/appScreenshots`)) {
const st = s.attributes.assetDeliveryState;
if (st?.state === 'FAILED') { bad++; warn('apple', `${loc.attributes.locale} ${set.attributes.screenshotDisplayType} ${s.attributes.fileName}: ${JSON.stringify(st.errors)}`); }
}
done('apple', bad ? `${bad} screenshots failed processing (re-run to retry)` : 'screenshots done');
}

36
cli/apple/status.ts Normal file
View file

@ -0,0 +1,36 @@
// Read-only overview: app record, versions + attached build, recent builds, in-app purchases, review submissions.
// crt-ship apple status [--json]
// And the one write it offers: cancel a review submission (to re-add first IAPs to it from the web UI):
// crt-ship apple status --cancel-submission <id> [--yes]
import type { Ctx } from '../lib/ctx';
import { confirm, need } from '../lib/ctx';
import { log } from '../lib/log';
import { ascClient } from './asc';
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId);
const appId = await c.app();
const cancel = ctx.args.get('cancel-submission');
if (cancel) {
if (!(await confirm(ctx, `Cancel review submission ${cancel}? (the version goes back to "Prepare for Submission")`))) return;
await c.asc('PATCH', `/v1/reviewSubmissions/${cancel}`, { data: { type: 'reviewSubmissions', id: cancel, attributes: { canceled: true } } });
log('apple', `review submission ${cancel} canceled; it takes a minute to settle`);
return;
}
const app = (await c.asc('GET', `/v1/apps/${appId}`)).data;
const versions = await c.all(`/v1/apps/${appId}/appStoreVersions?limit=10&include=build`);
const builds = (await c.asc('GET', `/v1/builds?filter[app]=${appId}&sort=-uploadedDate&limit=5&include=preReleaseVersion`));
const iaps = await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`);
const subs = await c.all(`/v1/reviewSubmissions?filter[app]=${appId}&limit=20`);
if (ctx.args.has('json')) { console.log(JSON.stringify({ app, versions, builds: builds.data, iaps, subs }, null, 2)); return; }
console.log(`${app.attributes.name} · ${app.attributes.bundleId} · Apple ID ${appId}`);
for (const v of versions) console.log(` version ${v.attributes.versionString.padEnd(8)} ${v.attributes.appVersionState ?? v.attributes.appStoreState}${v.relationships?.build?.data ? ' (build attached)' : ''}`);
const pre = new Map((builds.included ?? []).map((p: { id: string; attributes: { version: string } }) => [p.id, p.attributes.version]));
for (const b of builds.data) console.log(` build ${String(pre.get(b.relationships?.preReleaseVersion?.data?.id) ?? '?')} (${b.attributes.version}) ${b.attributes.processingState} ${b.attributes.uploadedDate?.slice(0, 10) ?? ''}`);
for (const p of iaps) console.log(` iap ${p.attributes.productId.padEnd(40)} ${p.attributes.inAppPurchaseType} ${p.attributes.state}`);
for (const s of subs) console.log(` review submission ${s.id} ${s.attributes.state} ${s.attributes.submittedDate?.slice(0, 10) ?? ''}`);
}

52
cli/check.ts Normal file
View file

@ -0,0 +1,52 @@
// Offline sanity check of everything the store commands read: config, both listing files, the screenshot copy, data
// safety answers, product texts vs store limits, and the files the config points at. No network, no credentials.
// crt-ship check
import { existsSync, readFileSync } from 'node:fs';
import { playCodes } from './lib/config';
import { len, parseAppStoreListing, parsePlayListing } from './lib/copy';
import type { Ctx } from './lib/ctx';
import { dataSafetyRows } from './lib/data-safety';
import { log, warn } from './lib/log';
import { type CopyFile, validateCopy } from './shots/lib';
export async function run(ctx: Ctx): Promise<void> {
const { config, resolve, path } = ctx.loaded;
log('check', `config ok: ${path}`);
const problems: string[] = [];
const tryDo = (what: string, fn: () => string): void => { try { log('check', `${what}: ${fn()}`); } catch (e) { problems.push(`${what}: ${(e as Error).message}`); } };
if (config.play) tryDo('play listing', () => {
const f = resolve(config.play!.listing ?? 'store/listing.md');
const copy = parsePlayListing(readFileSync(f, 'utf8'), config.app.name);
const want = config.languages.flatMap(playCodes);
const missing = want.filter(c => !copy.some(x => x.lang === c));
if (missing.length) warn('check', `play listing has no section for ${missing.join(', ')}`);
return `${copy.length} languages`;
});
if (config.apple) tryDo('appstore listing', () => {
const f = resolve(config.apple!.listing ?? 'store/appstore.md');
const copy = parseAppStoreListing(readFileSync(f, 'utf8'));
const missing = config.languages.filter(l => l.apple && !copy[l.apple]).map(l => l.apple);
if (missing.length) warn('check', `appstore listing has no section for ${missing.join(', ')}`);
return `${Object.keys(copy).length} locales`;
});
if (config.dataSafety) tryDo('data safety', () => `${dataSafetyRows(config.dataSafety!).length - 1} CSV rows`);
for (const p of config.products ?? []) for (const [lang, l] of Object.entries(p.listings)) {
if (config.apple && len(l.description) > 55) problems.push(`product ${p.id} ${lang}: description ${len(l.description)} > 55 (App Store limit)`);
if (len(l.description) > 200) problems.push(`product ${p.id} ${lang}: description ${len(l.description)} > 200 (Play limit)`);
}
if (config.shots) tryDo('shots copy', () => {
const copy = JSON.parse(readFileSync(resolve(config.shots!.copy), 'utf8')) as CopyFile;
const e = validateCopy(copy, config.shots!, config.languages.map(l => l.id));
if (e.length) throw new Error(e.join('; '));
return `${Object.keys(copy).length} languages x ${config.shots!.scenes.length} scenes`;
});
const files = [config.icons?.composite, config.icons?.foreground, config.icons?.background, config.shots?.logo,
...(config.products ?? []).map(p => p.reviewScreenshot)].filter((f): f is string => !!f);
for (const f of new Set(files)) if (!existsSync(resolve(f))) problems.push(`missing file: ${f}`);
if (existsSync(resolve('.gitignore')) && !/^\/?\.crt-ship\/?$/m.test(readFileSync(resolve('.gitignore'), 'utf8')))
problems.push('.gitignore does not ignore .crt-ship/ (credentials live there)');
if (problems.length) { for (const p of problems) console.error(` - ${p}`); throw new Error(`${problems.length} problems`); }
log('check', 'all good');
}

118
cli/icons.ts Normal file
View file

@ -0,0 +1,118 @@
// One artwork -> every icon the stores, the native shells and the web build need. Needs ffmpeg (cwebp optional).
// crt-ship icons [--dry]
// In (icons in crt-ship.config.ts): composite (1024 opaque full icon), foreground (1024 RGBA subject on transparent)
// and background (1024 opaque) for the adaptive / maskable variants.
// Out:
// <out>/app-store-1024.png App Store icon, rgb24 (Apple rejects an alpha channel)
// <out>/play-512.png Play listing icon
// <web>/icon-512|192|180.png, favicon-64|32.png (256-colour PNGs), maskable-512.webp (or .png without cwebp)
// <capacitorAssets>/icon-only.png, icon-foreground.png, icon-background.png (then capacitorCommand runs)
// <androidRes>/mipmap-*/ic_launcher{,_round}.png redrawn from the adaptive layers (capacitor-assets crops the full
// square artwork into the legacy icons, which cuts off anything near the corners)
import { mkdirSync, rmSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import type { Ctx } from './lib/ctx';
import { need } from './lib/ctx';
import { done, log } from './lib/log';
/** The anydpi-v26 adaptive icon xml insets both layers 16.7 %, so the launcher mask (often a circle) sees the
* foreground's middle ~66 %: at ~0.7 the subject's corners sit just inside a circle mask. */
export const ADAPTIVE_FG = 0.7;
/** Web maskable icon: the subject inside the 80 % safe zone. */
export const MASKABLE_FG = 0.8;
export const LEGACY_SIZES = [['mdpi', 48], ['hdpi', 72], ['xhdpi', 96], ['xxhdpi', 144], ['xxxhdpi', 192]] as const;
const has = (bin: string): boolean => spawnSync('sh', ['-c', `command -v ${bin}`]).status === 0;
/** geq expressions for 4x supersampled masks: circle, and rounded square with radius 22 %. */
export function maskExpr(shape: 'circle' | 'squircle', S: number): string {
const h = S / 2;
if (shape === 'circle') return `if(lte(hypot(X-${h}+0.5,Y-${h}+0.5),${h}),255,0)`;
const r = S * 0.22, e = h - r;
return `if(lte(hypot(max(abs(X-${h}+0.5)-${e},0),max(abs(Y-${h}+0.5)-${e},0)),${r}),255,0)`;
}
export async function run(ctx: Ctx): Promise<void> {
const { config, resolve, root } = ctx.loaded;
const ic = need(config.icons, 'icons');
if (!has('ffmpeg')) throw new Error('ffmpeg is required (brew install ffmpeg / apt install ffmpeg)');
const dry = ctx.args.has('dry');
const outs: string[] = [];
const ff = (args: string[], out: string): void => {
outs.push(out);
if (dry) return;
mkdirSync(join(out, '..'), { recursive: true });
const r = spawnSync('ffmpeg', ['-v', 'error', '-y', ...args, out], { encoding: 'utf8' });
if (r.status !== 0) throw new Error(`ffmpeg failed for ${out}\n${r.stderr}`);
};
const composite = resolve(ic.composite);
const fg = ic.foreground ? resolve(ic.foreground) : '';
const bg = ic.background ? resolve(ic.background) : '';
const adaptiveK = ic.adaptiveScale ?? ADAPTIVE_FG;
const tmp = join(root, '.crt-ship', 'tmp-icons');
mkdirSync(tmp, { recursive: true });
const opaque = (size: number, out: string, palette = false): void => {
const scale = `scale=${size}:${size}:flags=lanczos`;
if (!palette) ff(['-i', composite, '-vf', scale, '-pix_fmt', 'rgb24'], out);
else ff(['-i', composite, '-vf', `${scale},split[a][b];[a]palettegen=max_colors=256:stats_mode=full[p];[b][p]paletteuse=dither=sierra2_4a`, '-frames:v', '1'], out);
};
/** foreground scaled to k, centred on the background (opaque) or on transparency. */
const inset = (k: number, out: string, onBackground: boolean): void => {
const s = Math.round(1024 * k), o = Math.round((1024 - s) / 2);
const base = onBackground ? ['-i', bg] : ['-f', 'lavfi', '-i', 'color=c=black@0.0:s=1024x1024,format=rgba'];
ff([...base, '-i', fg, '-filter_complex', `[1:v]scale=${s}:${s}:flags=lanczos[f];[0:v][f]overlay=${o}:${o}:format=auto`, '-frames:v', '1',
'-pix_fmt', onBackground ? 'rgb24' : 'rgba'], out);
};
const out = resolve(ic.out);
opaque(1024, join(out, 'app-store-1024.png'));
opaque(512, join(out, 'play-512.png'));
if (ic.web) {
const web = resolve(ic.web);
for (const n of [512, 192, 180]) opaque(n, join(web, `icon-${n}.png`), true);
for (const n of [64, 32]) opaque(n, join(web, `favicon-${n}.png`), true);
if (fg && bg) {
inset(ic.maskableScale ?? MASKABLE_FG, join(tmp, 'maskable-1024.png'), true);
const png512 = has('cwebp') ? join(tmp, 'maskable-512.png') : join(web, 'maskable-512.png');
ff(['-i', join(tmp, 'maskable-1024.png'), '-vf', 'scale=512:512:flags=lanczos'], png512);
if (has('cwebp')) {
outs.push(join(web, 'maskable-512.webp'));
if (!dry && spawnSync('cwebp', ['-quiet', '-q', '86', png512, '-o', join(web, 'maskable-512.webp')]).status !== 0) throw new Error('cwebp failed');
}
}
}
if (ic.capacitorAssets) {
if (!fg || !bg) throw new Error('icons.capacitorAssets needs icons.foreground and icons.background');
const cap = resolve(ic.capacitorAssets);
opaque(1024, join(cap, 'icon-only.png'));
ff(['-i', bg, '-vf', 'scale=1024:1024:flags=lanczos', '-pix_fmt', 'rgb24'], join(cap, 'icon-background.png'));
inset(adaptiveK, join(cap, 'icon-foreground.png'), false);
if (ic.capacitorCommand && !dry) {
log('icons', `running: ${ic.capacitorCommand}`);
const r = spawnSync('sh', ['-c', ic.capacitorCommand], { cwd: root, encoding: 'utf8', stdio: 'inherit', env: { ...process.env, LANG: 'en_US.UTF-8', LC_ALL: 'en_US.UTF-8' } });
if (r.status !== 0) throw new Error('capacitorCommand failed');
}
}
// after capacitor-assets, so these legacy icons win
if (ic.androidRes) {
const res = resolve(ic.androidRes);
const adaptive = join(tmp, 'adaptive.png');
inset(adaptiveK, adaptive, true);
for (const [dpi, n] of LEGACY_SIZES) {
const S = n * 4;
for (const [name, shape] of [['ic_launcher_round', 'circle'], ['ic_launcher', 'squircle']] as const)
ff(['-i', adaptive, '-f', 'lavfi', '-i', `color=c=black:s=${S}x${S},format=gray,geq=lum='${maskExpr(shape, S)}'`, '-filter_complex',
`[0:v]scale=${S}:${S}:flags=lanczos,format=rgba[i];[i][1:v]alphamerge,scale=${n}:${n}:flags=area`, '-frames:v', '1', '-pix_fmt', 'rgba'],
join(res, `mipmap-${dpi}`, `${name}.png`));
}
}
rmSync(tmp, { recursive: true, force: true });
for (const f of outs.filter(f => !f.startsWith(tmp))) log('icons', dry ? `would write ${f.slice(root.length + 1)}` : `${f.slice(root.length + 1).padEnd(60)} ${(statSync(f).size / 1024).toFixed(1)} KB`);
done('icons', dry ? 'icons planned (dry run)' : 'icons done');
}

57
cli/index.ts Executable file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env bun
// crt-ship: ship a mobile game to Google Play and the App Store from the terminal.
// Run `crt-ship help` for the command list. Config: crt-ship.config.ts; credentials: env or .crt-ship/.
import { parseArgs } from './lib/args';
import { loadConfig } from './lib/config';
import type { Ctx } from './lib/ctx';
type Mod = { run(ctx: Ctx): Promise<void> };
const COMMANDS: Record<string, [() => Promise<Mod>, string]> = {
'check': [() => import('./check'), 'validate config, copy files, data safety answers and products (offline)'],
'play products': [() => import('./play/products'), 'one-time products: create / update, regional prices, activate'],
'play listing': [() => import('./play/listing'), 'app details + title / short / full description per language'],
'play images': [() => import('./play/images'), 'icon, feature graphic, phone / 7" / 10" screenshots per language'],
'play data-safety': [() => import('./play/data-safety'), 'Data safety form (CSV import through the API)'],
'play release': [() => import('./play/release'), 'upload an AAB and put it on tracks with release notes'],
'play status': [() => import('./play/status'), 'tracks, listings, products (read-only)'],
'apple bundle': [() => import('./apple/bundle'), 'register the bundle id; finds the app record'],
'apple iap': [() => import('./apple/iap'), 'in-app purchases, localizations, price schedules, availability'],
'apple iap-review': [() => import('./apple/iap-review'), 'IAP review screenshots + product states'],
'apple listing': [() => import('./apple/listing'), 'version, build, name / subtitle / description / keywords per locale'],
'apple screenshots': [() => import('./apple/screenshots'), '6.9" + 6.5" screenshots per locale'],
'apple appinfo': [() => import('./apple/appinfo'), 'categories, age rating, content rights, Free price, territories'],
'apple review-details': [() => import('./apple/review-details'), 'App Review contact + notes'],
'apple status': [() => import('./apple/status'), 'versions, builds, IAPs, review submissions (--cancel-submission <id>)'],
'icons': [() => import('./icons'), 'App Store / Play / web / adaptive / legacy launcher icons from one artwork'],
'shots capture': [() => import('./shots/capture'), 'raw frames from a running build of your game (playwright)'],
'shots fonts': [() => import('./shots/fonts'), 'download font subsets for the copy (CJK / Arabic / Cyrillic ...)'],
'shots render': [() => import('./shots/render'), 'store screenshots + feature graphic in every size and language'],
'shots jpeg': [() => import('./shots/jpeg'), 'JPEG q92 copies of the Play renders (4x faster uploads)'],
};
function help(): void {
console.log(`crt-ship · ship mobile games to Google Play and the App Store
usage: crt-ship <command> [options]
init copy the templates (config, listing, copy, privacy policy) into this project
${Object.entries(COMMANDS).map(([k, [, d]]) => ` ${k.padEnd(25)} ${d}`).join('\n')}
common options: --config <path> --dry (no writes) --only <ids> --yes (skip confirmations)
credentials: PLAY_SERVICE_ACCOUNT, ASC_KEY_ID, ASC_ISSUER_ID, ASC_P8 (env or .crt-ship/credentials.json)
docs: README.md and the skills/ folder of the toolchain`);
}
async function main(): Promise<void> {
const argv = process.argv.slice(2);
const [a, b] = argv;
if (!a || a === 'help' || a === '--help' || a === '-h') return help();
if (a === 'init') return (await import('./init')).run(parseArgs(argv.slice(1)));
const key = COMMANDS[`${a} ${b}`] ? `${a} ${b}` : COMMANDS[a] ? a : '';
if (!key) { console.error(`unknown command: ${argv.slice(0, 2).join(' ')}\n`); help(); process.exit(2); }
const args = parseArgs(argv.slice(key.split(' ').length));
const loaded = await loadConfig(args.get('config'));
await (await COMMANDS[key]![0]()).run({ loaded, args });
}
main().catch(e => { console.error(`\nerror: ${(e as Error).message}`); process.exit(1); });

37
cli/init.ts Normal file
View file

@ -0,0 +1,37 @@
// Copies the templates into the current project without overwriting anything, and makes sure .crt-ship/ is ignored.
// crt-ship init [--dir .]
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import type { Args } from './lib/args';
import { log } from './lib/log';
const T = join(import.meta.dir, '..', 'templates');
const FILES: [string, string][] = [
['crt-ship.config.ts', 'crt-ship.config.ts'],
['listing.md', 'store/listing.md'],
['appstore.md', 'store/appstore.md'],
['shots-copy.json', 'store/shots/copy.json'],
['app-privacy.md', 'store/app-privacy.md'],
['declarations.md', 'store/declarations.md'],
['privacy-policy.html', 'store/privacy-policy.html'],
['credentials.example.json', '.crt-ship/credentials.json'],
];
export async function run(args: Args): Promise<void> {
const root = resolve(args.str('dir', '.'));
for (const [from, to] of FILES) {
const dest = join(root, to);
if (existsSync(dest)) { log('init', `keep ${to} (exists)`); continue; }
mkdirSync(dirname(dest), { recursive: true });
copyFileSync(join(T, from), dest);
log('init', `create ${to}`);
}
const gi = join(root, '.gitignore');
const cur = existsSync(gi) ? readFileSync(gi, 'utf8') : '';
if (!/^\/?\.crt-ship\/?$/m.test(cur)) {
if (cur) appendFileSync(gi, `${cur.endsWith('\n') ? '' : '\n'}# crt-ship: credentials, fonts, temp files\n.crt-ship/\n`);
else writeFileSync(gi, '# crt-ship: credentials, fonts, temp files\n.crt-ship/\n');
log('init', '.gitignore: added .crt-ship/');
}
log('init', 'next: edit crt-ship.config.ts and .crt-ship/credentials.json, then `crt-ship check`');
}

34
cli/lib/args.ts Normal file
View file

@ -0,0 +1,34 @@
// Tiny argv parser: positionals, --flag, --key value, --key=value.
export interface Args {
_: string[];
has(name: string): boolean;
get(name: string): string | undefined;
str(name: string, d: string): string;
num(name: string, d: number): number;
list(name: string): string[];
}
export function parseArgs(argv: string[], booleans: string[] = []): Args {
const flags = new Map<string, string>();
const _: string[] = [];
const bool = new Set(['dry', 'yes', 'help', 'json', 'verbose', ...booleans]);
for (let i = 0; i < argv.length; i++) {
const a = argv[i]!;
if (!a.startsWith('--')) { _.push(a); continue; }
const eq = a.indexOf('=');
if (eq > 0) { flags.set(a.slice(2, eq), a.slice(eq + 1)); continue; }
const name = a.slice(2);
const next = argv[i + 1];
if (bool.has(name) || name.startsWith('no-') || next === undefined || next.startsWith('--')) flags.set(name, 'true');
else { flags.set(name, next); i++; }
}
const get = (n: string) => flags.get(n);
return {
_,
has: n => flags.has(n),
get,
str: (n, d) => get(n) ?? d,
num: (n, d) => { const v = get(n); const x = v === undefined ? d : Number(v); if (Number.isNaN(x)) throw new Error(`--${n}: not a number`); return x; },
list: n => (get(n) ?? '').split(',').map(s => s.trim()).filter(Boolean),
};
}

252
cli/lib/config.ts Normal file
View file

@ -0,0 +1,252 @@
// Per-project config (crt-ship.config.ts at the project root). Everything game-specific lives here; credentials never
// do (see credentials.ts). The file default-exports a plain object; `import type { ShipConfig }` is optional.
import { existsSync } from 'node:fs';
import { dirname, isAbsolute, join, resolve } from 'node:path';
export type ProductType = 'consumable' | 'non_consumable';
export interface PriceOverride {
/** ISO 3166-1 alpha-2 region for Play, e.g. "TR". */
region: string;
/** App Store territory (alpha-3), e.g. "TUR". Looked up from `region` when left out. */
territory?: string;
/** ISO 4217 currency, e.g. "TRY". */
currency: string;
/** Customer price in that currency, e.g. 129.99. Apple needs an existing price point with exactly this value. */
price: number;
}
export interface ProductListing { title: string; description: string }
export interface Product {
/** Store product id (same on both stores), e.g. "com.example.game.no_ads". */
id: string;
type: ProductType;
/** Base price in USD. Play converts it per region (convertRegionPrices); Apple equalizes it from the USA base. */
usd: number;
overrides?: PriceOverride[];
/** Language id (see `languages`) -> title + description. Apple caps the description at 55 characters. */
listings: Record<string, ProductListing>;
/** Apple only: internal reference name (defaults to the first listing title). */
referenceName?: string;
/** Apple only: note for the reviewer on this product. */
reviewNote?: string;
/** Apple only: review screenshot (path relative to the project root). */
reviewScreenshot?: string;
familySharable?: boolean;
}
export interface Language {
/** Your id for the language; also the folder name under the renders dir and the key in copy files. */
id: string;
/** Play listing language code(s), e.g. "en-US" or ["es-ES", "es-419"]. Leave out to skip on Play. */
play?: string | string[];
/** App Store Connect locale, e.g. "en-US", "tr", "zh-Hans". Leave out to skip on the App Store. */
apple?: string;
rtl?: boolean;
}
export type PlayPurpose = 'functionality' | 'analytics' | 'communications' | 'fraud' | 'advertising' | 'personalization' | 'account';
export interface DataUsage {
/** Transferred to a third party that is not your service provider (ad networks usually are "shared"). */
shared: boolean;
ephemeral?: boolean;
/** Only true when the user can really choose (Play rejects "optional" otherwise). */
optional?: boolean;
collect: PlayPurpose[];
share?: PlayPurpose[];
}
export interface DataSafety {
collectsData: boolean;
encryptedInTransit: boolean;
deletionRequest: boolean;
/** Response ids for PSL_SUPPORTED_ACCOUNT_CREATION_METHODS. Default ["PSL_ACM_NONE"] (no accounts). */
accountCreation?: string[];
/** Play data type id (e.g. "PSL_DEVICE_ID") -> usage. Types not listed are "not collected". */
data: Record<string, DataUsage>;
}
export interface ShotBadgeSlot { y: number; side: 'l' | 'r'; icon?: string; iconBg?: string; hot?: boolean; stars?: boolean; rot?: number }
export interface ShotFrame { src: string; x?: number; dy?: number; scale?: number; rotate?: number }
export interface Palette { bg: [string, string, string]; glow: string; ink: string; shadow: string }
export interface ShotScene { id: string; palette: string | Palette; hero?: boolean; frames: ShotFrame[]; badges?: ShotBadgeSlot[]; over?: number }
export interface ShotsConfig {
/** JSON file: { [languageId]: { [sceneId]: { eyebrow, title: [line1, line2], sub, badges: [[big, small?]] } } } */
copy: string;
/** Raw frames: <frames>/<languageId>/<phone|tablet>/<name>.png */
frames: string;
/** Output: <out>/<languageId>/<size>/NN.png and feature/1024x500.png */
out: string;
/** Optional logo image for the hero scene and the feature graphic. */
logo?: string;
/** Google Fonts families for titles / body text (downloaded and subset by `shots fonts`). */
fonts?: { title?: string; body?: string };
scenes: ShotScene[];
feature?: { frame: string; palette?: string | Palette };
/** Phone screen size of the raw frames in CSS px (default 440 x 902, iPhone 6.9" minus the 54 px status bar). */
phone?: { w: number; h: number; dpr?: number };
tablet?: { w: number; h: number; dpr?: number };
/** Optional capture of raw frames from a running build of your game. */
capture?: {
baseUrl: string;
/** Appended to every URL, e.g. "noads=1&noanalytics=1". "{lang}" is replaced. */
query?: string;
shots: Record<string, { path: string; waitFor?: string; delayMs?: number; eval?: string[]; device?: 'phone' | 'tablet' | 'both' }>;
};
}
export interface IconsConfig {
/** 1024 opaque composite (the full icon). */
composite: string;
/** 1024 RGBA subject on transparent background (adaptive foreground). */
foreground?: string;
/** 1024 opaque background (adaptive background). */
background?: string;
/** Output dir for the store icons (app-store-1024.png, play-512.png). */
out: string;
/** Web icons (icon-512/192/180, favicon-64/32, maskable-512). */
web?: string;
/** @capacitor/assets source dir (icon-only, icon-foreground, icon-background). */
capacitorAssets?: string;
/** Command to run after writing the capacitor sources, e.g. "bunx @capacitor/assets generate". */
capacitorCommand?: string;
/** Android res dir; legacy ic_launcher{,_round}.png get redrawn from the adaptive layers. */
androidRes?: string;
adaptiveScale?: number;
maskableScale?: number;
}
export interface AppleAppInfo {
categories?: { primary: string; primarySubOne?: string; primarySubTwo?: string; secondary?: string };
/** Overrides for the age rating declaration; everything else is NONE / false. */
ageRating?: Record<string, string | boolean | null>;
contentRights?: 'DOES_NOT_USE_THIRD_PARTY_CONTENT' | 'USES_THIRD_PARTY_CONTENT';
/** Free app, available in every territory and new ones. */
free?: boolean;
reviewNotes?: string;
}
export interface ShipConfig {
app: {
name: string;
copyright?: string;
privacyPolicyUrl: string;
supportUrl: string;
marketingUrl?: string;
website: string;
contactEmail: string;
};
languages: Language[];
play?: { packageName: string; defaultLanguage?: string; listing?: string; icon?: string };
apple?: { bundleId: string; appId?: string; primaryLocale?: string; listing?: string; info?: AppleAppInfo; reviewShots?: string };
products?: Product[];
dataSafety?: DataSafety;
/** Rendered store graphics dir (<renders>/<languageId>/<size>/NN.png|jpg). Defaults to shots.out. */
renders?: string;
shots?: ShotsConfig;
icons?: IconsConfig;
}
/** Identity helper for typed configs: `export default defineConfig({...})`. */
export const defineConfig = (c: ShipConfig): ShipConfig => c;
const ID_RE = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)+$/;
const PRODUCT_RE = /^[A-Za-z0-9._]+$/;
const URL_RE = /^https:\/\/\S+$/;
/** Every problem with a config, empty when it is fine. Pure (no IO). */
export function validateConfig(c: unknown): string[] {
const e: string[] = [];
const cfg = c as Partial<ShipConfig>;
if (!cfg || typeof cfg !== 'object') return ['config must be an object'];
const app = cfg.app;
if (!app) e.push('app: missing');
else {
if (!app.name) e.push('app.name: missing');
for (const k of ['privacyPolicyUrl', 'supportUrl', 'website'] as const) if (!URL_RE.test(app[k] ?? '')) e.push(`app.${k}: must be an https URL`);
if (app.marketingUrl && !URL_RE.test(app.marketingUrl)) e.push('app.marketingUrl: must be an https URL');
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(app.contactEmail ?? '')) e.push('app.contactEmail: not an email address');
}
const langs = cfg.languages;
if (!Array.isArray(langs) || !langs.length) e.push('languages: at least one language');
const ids = new Set<string>();
for (const [i, l] of (langs ?? []).entries()) {
if (!l.id) e.push(`languages[${i}].id: missing`);
if (ids.has(l.id)) e.push(`languages[${i}].id: duplicate "${l.id}"`);
ids.add(l.id);
}
if (cfg.play && !ID_RE.test(cfg.play.packageName ?? '')) e.push('play.packageName: not a package name (com.example.game)');
if (cfg.apple) {
if (!ID_RE.test(cfg.apple.bundleId ?? '')) e.push('apple.bundleId: not a bundle id (com.example.game)');
if (cfg.apple.appId && !/^\d+$/.test(cfg.apple.appId)) e.push('apple.appId: numeric Apple ID of the app (App Store Connect > App Information)');
}
const seen = new Set<string>();
for (const [i, p] of (cfg.products ?? []).entries()) {
const at = `products[${i}]`;
if (!PRODUCT_RE.test(p.id ?? '')) e.push(`${at}.id: letters, digits, "." and "_" only`);
if (seen.has(p.id)) e.push(`${at}.id: duplicate "${p.id}"`);
seen.add(p.id);
if (p.type !== 'consumable' && p.type !== 'non_consumable') e.push(`${at}.type: consumable | non_consumable`);
if (!(typeof p.usd === 'number' && p.usd > 0)) e.push(`${at}.usd: positive number`);
if (!p.listings || !Object.keys(p.listings).length) e.push(`${at}.listings: at least one language`);
for (const [lang, l] of Object.entries(p.listings ?? {})) {
if (!ids.has(lang)) e.push(`${at}.listings.${lang}: not in languages`);
if (!l.title) e.push(`${at}.listings.${lang}.title: missing`);
if ([...(l.title ?? '')].length > 30) e.push(`${at}.listings.${lang}.title: over 30 characters (App Store limit)`);
if (!l.description) e.push(`${at}.listings.${lang}.description: missing`);
}
for (const o of p.overrides ?? []) {
if (!/^[A-Z]{2}$/.test(o.region)) e.push(`${at}.overrides: region "${o.region}" must be ISO alpha-2 (TR)`);
if (!/^[A-Z]{3}$/.test(o.currency)) e.push(`${at}.overrides: currency "${o.currency}" must be ISO 4217 (TRY)`);
if (!(o.price > 0)) e.push(`${at}.overrides: price must be positive`);
}
}
if (cfg.dataSafety) {
for (const [t, u] of Object.entries(cfg.dataSafety.data ?? {})) {
if (!/^PSL_[A-Z_]+$/.test(t)) e.push(`dataSafety.data.${t}: use the Play type id (PSL_DEVICE_ID, ...)`);
if (!u.collect?.length) e.push(`dataSafety.data.${t}: at least one collection purpose`);
if (u.shared && !u.share?.length) e.push(`dataSafety.data.${t}: shared, so it needs sharing purposes`);
}
}
if (cfg.shots) {
const s = cfg.shots;
for (const k of ['copy', 'frames', 'out'] as const) if (!s[k]) e.push(`shots.${k}: missing`);
if (!s.scenes?.length) e.push('shots.scenes: at least one scene');
for (const sc of s.scenes ?? []) if (!sc.frames?.length) e.push(`shots.scenes[${sc.id}].frames: at least one frame`);
}
if (cfg.icons) {
if (!cfg.icons.composite) e.push('icons.composite: missing');
if (!cfg.icons.out) e.push('icons.out: missing');
if (cfg.icons.androidRes && !(cfg.icons.foreground && cfg.icons.background)) e.push('icons.androidRes needs icons.foreground + icons.background');
}
return e;
}
export interface Loaded { config: ShipConfig; root: string; path: string; resolve(p: string): string }
const NAMES = ['crt-ship.config.ts', 'crt-ship.config.js', 'crt-ship.config.json'];
/** Finds crt-ship.config.* from `start` upwards (or uses `explicit`), imports and validates it. */
export async function loadConfig(explicit?: string, start = process.cwd()): Promise<Loaded> {
let path = explicit ? resolve(explicit) : '';
if (!path) {
for (let d = resolve(start); ; d = dirname(d)) {
const hit = NAMES.map(n => join(d, n)).find(existsSync);
if (hit) { path = hit; break; }
if (dirname(d) === d) break;
}
}
if (!path || !existsSync(path)) throw new Error('no crt-ship.config.ts found (run `crt-ship init` or pass --config)');
const mod = await import(path);
const config = (mod.default ?? mod) as ShipConfig;
const errors = validateConfig(config);
if (errors.length) throw new Error(`${path}:\n ` + errors.join('\n '));
const root = dirname(path);
return { config, root, path, resolve: (p: string) => (isAbsolute(p) ? p : join(root, p)) };
}
/** Play language codes of a language entry. */
export const playCodes = (l: Language): string[] => (l.play === undefined ? [] : Array.isArray(l.play) ? l.play : [l.play]);

81
cli/lib/copy.ts Normal file
View file

@ -0,0 +1,81 @@
// Store copy lives in two human-edited Markdown files (templates/listing.md, templates/appstore.md). Pure parsers +
// limit checks; the consoles count characters (code points), so do we.
export const PLAY_LIMITS = { title: 30, short: 80, full: 4000, whatsNew: 500 } as const;
export const APPLE_LIMITS = { name: 30, subtitle: 30, promotionalText: 170, keywords: 100, description: 4000, whatsNew: 4000 } as const;
export interface PlayCopy { lang: string; title: string; short: string; full: string; whatsNew: string }
export type AppleField = keyof typeof APPLE_LIMITS;
export type AppleCopy = Partial<Record<AppleField, string>>;
export const len = (s: string): number => [...s].length;
/** "### heading" sub-sections of a "## " section, each with its first fenced block. */
function blocks(section: string): { heading: string; text: string }[] {
const out: { heading: string; text: string }[] = [];
for (const part of section.split(/^### /m).slice(1)) {
const heading = part.split('\n')[0]!.trim();
const m = /^```[^\n]*\n([\s\S]*?)\n```/m.exec(part);
if (m) out.push({ heading, text: m[1]!.trim() });
}
return out;
}
/**
* Play copy. Every "## ..." section with a `<!-- play: en-US[, en-GB] -->` line contributes, under "### " headings
* that contain (case-insensitive): "title" (optional, defaults to `defaultTitle`), "short", "full", "what's new"
* (or "release notes"). One section can serve several Play languages.
*/
export function parsePlayListing(md: string, defaultTitle: string): PlayCopy[] {
const out: PlayCopy[] = [];
const errors: string[] = [];
for (const sec of md.split(/^## /m).slice(1)) {
const m = /^<!--\s*play:\s*([^>]+?)\s*-->$/m.exec(sec);
if (!m) continue;
const name = sec.split('\n')[0]!.trim();
const bs = blocks(sec);
const find = (re: RegExp): string | undefined => bs.find(b => re.test(b.heading))?.text;
const title = find(/\btitle\b/i) ?? defaultTitle;
const short = find(/\bshort\b/i);
const full = find(/\bfull\b/i);
const whatsNew = find(/what'?s new|release notes/i) ?? '';
if (!short) errors.push(`"${name}": no "### ... short ..." block`);
if (!full) errors.push(`"${name}": no "### ... full ..." block`);
if (!short || !full) continue;
for (const lang of m[1]!.split(',').map(s => s.trim()).filter(Boolean)) {
const c: PlayCopy = { lang, title, short, full, whatsNew };
for (const k of Object.keys(PLAY_LIMITS) as (keyof typeof PLAY_LIMITS)[])
if (len(c[k]) > PLAY_LIMITS[k]) errors.push(`${lang}: ${k} is ${len(c[k])} > ${PLAY_LIMITS[k]}`);
if (out.some(o => o.lang === lang)) errors.push(`${lang}: listed in two sections`);
out.push(c);
}
}
if (errors.length) throw new Error('listing: ' + errors.join('; '));
return out;
}
const APPLE_REQUIRED: AppleField[] = ['subtitle', 'promotionalText', 'keywords', 'description'];
/**
* App Store copy. One "## <ASC locale> ..." section per locale (the first word is the locale), with "### <field>"
* headings named exactly like the API attributes: name (optional), subtitle, promotionalText, keywords, description,
* whatsNew (optional; not allowed on a first version).
*/
export function parseAppStoreListing(md: string): Record<string, AppleCopy> {
const out: Record<string, AppleCopy> = {};
const errors: string[] = [];
for (const sec of md.split(/^## /m).slice(1)) {
const locale = sec.split(/[\s·]/)[0]!.trim();
if (!/^[a-z]{2,3}(-[A-Za-z]{2,4})?$/.test(locale)) continue; // prose sections
const f: AppleCopy = {};
for (const b of blocks(sec)) if (b.heading in APPLE_LIMITS) f[b.heading as AppleField] = b.text;
for (const k of APPLE_REQUIRED) if (!f[k]) errors.push(`${locale}: missing ${k}`);
for (const [k, v] of Object.entries(f) as [AppleField, string][])
if (len(v) > APPLE_LIMITS[k]) errors.push(`${locale}: ${k} is ${len(v)} > ${APPLE_LIMITS[k]}`);
if (f.keywords && Buffer.byteLength(f.keywords) > 100) errors.push(`${locale}: keywords are ${Buffer.byteLength(f.keywords)} bytes > 100`);
if (f.keywords && /,\s/.test(f.keywords)) errors.push(`${locale}: keywords: no spaces after commas (they count)`);
out[locale] = f;
}
if (errors.length) throw new Error('appstore listing: ' + errors.join('; '));
return out;
}

61
cli/lib/credentials.ts Normal file
View file

@ -0,0 +1,61 @@
// Credentials never live in crt-ship.config.ts or in git. Sources, first hit wins:
// 1. environment: PLAY_SERVICE_ACCOUNT (path to the JSON key, or the JSON itself), ASC_KEY_ID, ASC_ISSUER_ID,
// ASC_P8 (path to AuthKey_<id>.p8, or the PEM itself)
// 2. <project>/.crt-ship/credentials.json (gitignored), same names:
// { "PLAY_SERVICE_ACCOUNT": "...", "ASC_KEY_ID": "...", "ASC_ISSUER_ID": "...", "ASC_P8": "...",
// "reviewContact": { "firstName": "", "lastName": "", "phone": "", "email": "" } }
// 3. ASC_P8 only: ~/.appstoreconnect/private_keys/AuthKey_<ASC_KEY_ID>.p8 (where Xcode / altool keep it)
import { existsSync, readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
export interface ServiceAccount { client_email: string; private_key: string; token_uri?: string }
export interface AscKey { keyId: string; issuerId: string; pem: string }
export interface ReviewContact { firstName: string; lastName: string; phone: string; email: string }
type Store = Record<string, unknown>;
function fileStore(root: string): Store {
const p = join(root, '.crt-ship', 'credentials.json');
if (!existsSync(p)) return {};
try { return JSON.parse(readFileSync(p, 'utf8')) as Store; } catch (e) { throw new Error(`${p}: ${(e as Error).message}`); }
}
function value(root: string, name: string): string | undefined {
const env = process.env[name];
if (env) return env;
const v = fileStore(root)[name];
return typeof v === 'string' && v ? v : undefined;
}
/** A path or the inline content itself. */
const inline = (v: string, marker: RegExp): string => (marker.test(v) ? v : readFileSync(v.replace(/^~(?=\/)/, homedir()), 'utf8'));
export function parseServiceAccount(text: string): ServiceAccount {
const j = JSON.parse(text) as Partial<ServiceAccount>;
if (typeof j.client_email !== 'string' || typeof j.private_key !== 'string' || !j.private_key.includes('PRIVATE KEY'))
throw new Error('not a Google service account JSON key (client_email / private_key missing)');
return { client_email: j.client_email, private_key: j.private_key, token_uri: j.token_uri };
}
export function playServiceAccount(root: string): ServiceAccount {
const v = value(root, 'PLAY_SERVICE_ACCOUNT');
if (!v) throw new Error('PLAY_SERVICE_ACCOUNT is not set (env, or .crt-ship/credentials.json): path to the service account JSON key');
return parseServiceAccount(inline(v, /^\s*\{/));
}
export function ascKey(root: string): AscKey {
const keyId = value(root, 'ASC_KEY_ID');
const issuerId = value(root, 'ASC_ISSUER_ID');
if (!keyId || !issuerId) throw new Error('ASC_KEY_ID and ASC_ISSUER_ID are not set (env, or .crt-ship/credentials.json)');
const p8 = value(root, 'ASC_P8') ?? join(homedir(), '.appstoreconnect/private_keys', `AuthKey_${keyId}.p8`);
if (!/BEGIN PRIVATE KEY/.test(p8) && !existsSync(p8.replace(/^~(?=\/)/, homedir()))) throw new Error(`App Store Connect key not found: ${p8} (set ASC_P8)`);
return { keyId, issuerId, pem: inline(p8, /BEGIN PRIVATE KEY/) };
}
/** App Review contact from .crt-ship/credentials.json (personal data, so never in the config). */
export function reviewContact(root: string): ReviewContact | null {
const c = fileStore(root).reviewContact as Partial<ReviewContact> | undefined;
if (!c?.email) return null;
return { firstName: c.firstName ?? '', lastName: c.lastName ?? '', phone: c.phone ?? '', email: c.email };
}

31
cli/lib/ctx.ts Normal file
View file

@ -0,0 +1,31 @@
import type { Args } from './args';
import type { Language, Loaded } from './config';
import { playCodes } from './config';
export interface Ctx { loaded: Loaded; args: Args }
export function need<T>(v: T | undefined, what: string): T {
if (v === undefined || v === null) throw new Error(`crt-ship.config.ts: ${what} is not set`);
return v;
}
/** Play language code -> language entry. */
export function byPlayCode(langs: Language[]): Map<string, Language> {
const m = new Map<string, Language>();
for (const l of langs) for (const c of playCodes(l)) m.set(c, l);
return m;
}
/** App Store locale -> language entry. */
export function byAppleLocale(langs: Language[]): Map<string, Language> {
return new Map(langs.filter(l => l.apple).map(l => [l.apple!, l] as const));
}
/** Asks on the terminal unless --yes. Store actions that are public or irreversible go through this. */
export async function confirm(ctx: Ctx, question: string): Promise<boolean> {
if (ctx.args.has('yes')) return true;
if (!process.stdin.isTTY) { console.log(`${question}\n not a terminal: re-run with --yes to confirm`); return false; }
process.stdout.write(`${question} [y/N] `);
for await (const line of console) return /^y(es)?$/i.test(line.trim());
return false;
}

69
cli/lib/data-safety.ts Normal file
View file

@ -0,0 +1,69 @@
// Google Play Data safety form as Play's CSV import format (the same layout Play Console exports from the Data safety
// page: "Export to CSV"). One row per question / choice. Importing replaces every earlier answer. Pure.
// Play names a missing answer "Response missing for <ID>" and a wrong choice "Invalid response ID <ID>".
import type { DataSafety, PlayPurpose } from './config';
export const PURPOSE_IDS: Record<PlayPurpose, string> = {
functionality: 'PSL_APP_FUNCTIONALITY',
analytics: 'PSL_ANALYTICS',
communications: 'PSL_DEVELOPER_COMMUNICATIONS',
fraud: 'PSL_FRAUD_PREVENTION_SECURITY',
advertising: 'PSL_ADVERTISING',
personalization: 'PSL_PERSONALIZATION',
account: 'PSL_ACCOUNT_MANAGEMENT',
};
const PURPOSES = Object.values(PURPOSE_IDS);
/** Every category and data type of the form, in form order. */
export const CATEGORIES: [string, string[]][] = [
['PSL_DATA_TYPES_PERSONAL', ['PSL_NAME', 'PSL_EMAIL', 'PSL_USER_ACCOUNT', 'PSL_ADDRESS', 'PSL_PHONE', 'PSL_RACE_ETHNICITY', 'PSL_POLITICAL_RELIGIOUS', 'PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY', 'PSL_OTHER_PERSONAL']],
['PSL_DATA_TYPES_FINANCIAL', ['PSL_PURCHASE_HISTORY', 'PSL_CREDIT_SCORE', 'PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER', 'PSL_OTHER']],
['PSL_DATA_TYPES_LOCATION', ['PSL_APPROX_LOCATION', 'PSL_PRECISE_LOCATION']],
['PSL_DATA_TYPES_SEARCH_AND_BROWSING', ['PSL_WEB_BROWSING_HISTORY']],
['PSL_DATA_TYPES_EMAIL_AND_TEXT', ['PSL_EMAILS', 'PSL_SMS_CALL_LOG', 'PSL_OTHER_MESSAGES']],
['PSL_DATA_TYPES_PHOTOS_AND_VIDEOS', ['PSL_PHOTOS', 'PSL_VIDEOS']],
['PSL_DATA_TYPES_AUDIO', ['PSL_AUDIO', 'PSL_MUSIC', 'PSL_OTHER_AUDIO']],
['PSL_DATA_TYPES_HEALTH_AND_FITNESS', ['PSL_HEALTH', 'PSL_FITNESS']],
['PSL_DATA_TYPES_CONTACTS', ['PSL_CONTACTS']],
['PSL_DATA_TYPES_CALENDAR', ['PSL_CALENDAR']],
['PSL_DATA_TYPES_APP_PERFORMANCE', ['PSL_CRASH_LOGS', 'PSL_PERFORMANCE_DIAGNOSTICS', 'PSL_OTHER_PERFORMANCE']],
['PSL_DATA_TYPES_FILES_AND_DOCS', ['PSL_FILES_AND_DOCS']],
['PSL_DATA_TYPES_APP_ACTIVITY', ['PSL_USER_INTERACTION', 'PSL_IN_APP_SEARCH_HISTORY', 'PSL_APPS_ON_DEVICE', 'PSL_USER_GENERATED_CONTENT', 'PSL_OTHER_APP_ACTIVITY']],
['PSL_DATA_TYPES_IDENTIFIERS', ['PSL_DEVICE_ID']],
];
export const ALL_TYPES = new Set(CATEGORIES.flatMap(([, t]) => t));
const HEADER = ['Question ID (machine readable)', 'Response ID (machine readable)', 'Response value', 'Answer requirement', 'Human-friendly question label'];
export function csvCell(s: string): string { return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; }
export function dataSafetyRows(ds: DataSafety): string[][] {
for (const t of Object.keys(ds.data)) if (!ALL_TYPES.has(t)) throw new Error(`dataSafety: unknown data type ${t}`);
const b = (v: boolean) => (v ? 'TRUE' : 'FALSE');
const pick = (v: boolean) => (v ? 'TRUE' : '');
const rows: string[][] = [HEADER];
rows.push(['PSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA', '', b(ds.collectsData), 'REQUIRED', 'Does your app collect or share any of the required user data types?']);
rows.push(['PSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT', '', b(ds.encryptedInTransit), 'MAYBE_REQUIRED', 'Is all of the user data collected by your app encrypted in transit?']);
rows.push(['PSL_DATA_COLLECTION_USER_REQUEST_DELETE', '', b(ds.deletionRequest), 'MAYBE_REQUIRED', 'Do you provide a way for users to request that their data is deleted?']);
// Newer form question that Google's older sample CSV does not have; the import fails without it.
for (const id of ds.accountCreation ?? ['PSL_ACM_NONE'])
rows.push(['PSL_SUPPORTED_ACCOUNT_CREATION_METHODS', id, 'TRUE', 'MULTIPLE_CHOICE', `Account creation / ${id}`]);
for (const [cat, types] of CATEGORIES) for (const t of types) rows.push([cat, t, pick(!!ds.data[t]), 'MULTIPLE_CHOICE', `${cat} / ${t}`]);
for (const [, types] of CATEGORIES) for (const t of types) {
const u = ds.data[t];
const q = (s: string) => `PSL_DATA_USAGE_RESPONSES:${t}:${s}`;
const label = `Data usage and handling (${t})`;
const collect = new Set((u?.collect ?? []).map(p => PURPOSE_IDS[p]));
const share = new Set((u?.shared ? u.share ?? [] : []).map(p => PURPOSE_IDS[p]));
rows.push([q('PSL_DATA_USAGE_COLLECTION_AND_SHARING'), 'PSL_DATA_USAGE_ONLY_COLLECTED', pick(!!u), 'MULTIPLE_CHOICE', label + ' / Collected']);
rows.push([q('PSL_DATA_USAGE_COLLECTION_AND_SHARING'), 'PSL_DATA_USAGE_ONLY_SHARED', pick(!!u?.shared), 'MULTIPLE_CHOICE', label + ' / Shared']);
rows.push([q('PSL_DATA_USAGE_EPHEMERAL'), '', u ? b(!!u.ephemeral) : '', 'MAYBE_REQUIRED', label + ' / Processed ephemerally']);
rows.push([q('DATA_USAGE_USER_CONTROL'), 'PSL_DATA_USAGE_USER_CONTROL_OPTIONAL', pick(!!u?.optional), 'SINGLE_CHOICE', label + ' / Optional']);
rows.push([q('DATA_USAGE_USER_CONTROL'), 'PSL_DATA_USAGE_USER_CONTROL_REQUIRED', pick(!!u && !u.optional), 'SINGLE_CHOICE', label + ' / Required']);
for (const p of PURPOSES) rows.push([q('DATA_USAGE_COLLECTION_PURPOSE'), p, pick(collect.has(p)), 'MULTIPLE_CHOICE', label + ' / Collection purpose']);
for (const p of PURPOSES) rows.push([q('DATA_USAGE_SHARING_PURPOSE'), p, pick(share.has(p)), 'MULTIPLE_CHOICE', label + ' / Sharing purpose']);
}
return rows;
}
export const dataSafetyCsv = (ds: DataSafety): string => dataSafetyRows(ds).map(r => r.map(csvCell).join(',')).join('\n') + '\n';

40
cli/lib/google-auth.ts Normal file
View file

@ -0,0 +1,40 @@
// OAuth 2.0 service account flow (RS256 JWT assertion -> access token) with WebCrypto, no googleapis dependency.
import type { ServiceAccount } from './credentials';
export const PLAY_SCOPE = 'https://www.googleapis.com/auth/androidpublisher';
const b64u = (b: ArrayBuffer | string): string => Buffer.from(typeof b === 'string' ? b : new Uint8Array(b)).toString('base64url');
function pemToDer(pem: string): ArrayBuffer {
const bytes = Buffer.from(pem.replace(/-----(BEGIN|END) PRIVATE KEY-----/g, '').replace(/\s+/g, ''), 'base64');
const out = new ArrayBuffer(bytes.length);
new Uint8Array(out).set(bytes);
return out;
}
export async function signAssertion(sa: ServiceAccount, nowS: number, scope = PLAY_SCOPE): Promise<string> {
const key = await crypto.subtle.importKey('pkcs8', pemToDer(sa.private_key), { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']);
const head = b64u(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));
const claims = b64u(JSON.stringify({ iss: sa.client_email, scope, aud: sa.token_uri || 'https://oauth2.googleapis.com/token', iat: nowS, exp: nowS + 3600 }));
const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, new TextEncoder().encode(`${head}.${claims}`));
return `${head}.${claims}.${b64u(sig)}`;
}
/** Cached access token getter (refreshes 5 minutes before expiry). */
export function tokenSource(sa: ServiceAccount): () => Promise<string> {
let token = '';
let until = 0;
return async () => {
if (token && Date.now() < until) return token;
const assertion = await signAssertion(sa, Math.floor(Date.now() / 1000));
const r = await fetch(sa.token_uri || 'https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion }),
});
const j = (await r.json()) as { access_token?: string; expires_in?: number; error_description?: string };
if (!j.access_token) throw new Error('Google token: ' + (j.error_description ?? r.status));
token = j.access_token;
until = Date.now() + ((j.expires_in ?? 3600) - 300) * 1000;
return token;
};
}

12
cli/lib/log.ts Normal file
View file

@ -0,0 +1,12 @@
// Plain, line-based progress output: easy to follow live and easy to tail when a long upload runs in the background
// (`crt-ship play images > .crt-ship/images.log 2>&1 &`). Every line carries a timestamp and a tag.
const t0 = Date.now();
const stamp = (): string => new Date().toISOString().slice(11, 19);
const since = (): string => `${((Date.now() - t0) / 1000).toFixed(0)}s`;
export const log = (tag: string, msg: string): void => console.log(`${stamp()} [${tag}] ${msg}`);
export const warn = (tag: string, msg: string): void => console.warn(`${stamp()} [${tag}] WARN ${msg}`);
export const done = (tag: string, msg: string): void => console.log(`${stamp()} [${tag}] ${msg} (${since()})`);
/** "3/14 de-DE" style counters. */
export const step = (i: number, n: number, what: string): string => `${String(i).padStart(String(n).length)}/${n} ${what}`;

55
cli/lib/money.ts Normal file
View file

@ -0,0 +1,55 @@
// Price helpers shared by the Play and App Store product commands. Pure.
import type { PriceOverride } from './config';
export interface Money { currencyCode: string; units: string; nanos?: number }
/** 2.99 -> { units: "2", nanos: 990000000 } without float noise (works in cents). */
export function toMoney(currencyCode: string, value: number): Money {
if (!(value >= 0)) throw new Error(`bad price ${value}`);
const cents = Math.round(value * 100);
return { currencyCode, units: String(Math.floor(cents / 100)), nanos: (cents % 100) * 10_000_000 };
}
export const fromMoney = (m: Money): number => Number(m.units) + (m.nanos ?? 0) / 1e9;
export interface RegionConfig { regionCode: string; availability: 'AVAILABLE'; price: Money }
/**
* Play: take convertRegionPrices' result (every region Play sells in, rounded to local price points) and replace the
* regions you set explicitly. Throws when an override names a region Play does not offer.
*/
export function playRegionConfigs(converted: Record<string, { regionCode: string; price: Money }>, overrides: PriceOverride[] = []): RegionConfig[] {
const byRegion = new Map(Object.values(converted).map(r => [r.regionCode, r.price] as const));
for (const o of overrides) if (!byRegion.has(o.region)) throw new Error(`Play does not sell in region ${o.region}`);
const ov = new Map(overrides.map(o => [o.region, toMoney(o.currency, o.price)] as const));
return [...byRegion.entries()].sort(([a], [b]) => a.localeCompare(b))
.map(([regionCode, price]) => ({ regionCode, availability: 'AVAILABLE' as const, price: ov.get(regionCode) ?? price }));
}
/** Apple price points carry customerPrice as a string ("2.99", "30.0"); compare as cents. */
export const samePrice = (customerPrice: string | number, want: number): boolean =>
Math.round(Number(customerPrice) * 100) === Math.round(want * 100);
/** Nearest few price points, for a helpful error. */
export function nearestPrices(have: number[], want: number, n = 3): number[] {
return [...have].sort((a, b) => Math.abs(a - want) - Math.abs(b - want)).slice(0, n);
}
// ISO 3166 alpha-2 -> alpha-3 for the App Store territories (the 175 storefronts). Unknown: set `territory` yourself.
const A3 = `AE ARE AF AFG AG ATG AI AIA AL ALB AM ARM AO AGO AR ARG AT AUT AU AUS AZ AZE BA BIH BB BRB BE BEL BF BFA BG BGR
BH BHR BJ BEN BM BMU BN BRN BO BOL BR BRA BS BHS BT BTN BW BWA BY BLR BZ BLZ CA CAN CD COD CG COG CH CHE CI CIV CL CHL CM CMR
CN CHN CO COL CR CRI CV CPV CY CYP CZ CZE DE DEU DK DNK DM DMA DO DOM DZ DZA EC ECU EE EST EG EGY ES ESP FI FIN FJ FJI FM FSM
FR FRA GA GAB GB GBR GD GRD GE GEO GH GHA GM GMB GR GRC GT GTM GW GNB GY GUY HK HKG HN HND HR HRV HU HUN ID IDN IE IRL IL ISR
IN IND IQ IRQ IS ISL IT ITA JM JAM JO JOR JP JPN KE KEN KG KGZ KH KHM KN KNA KR KOR KW KWT KY CYM KZ KAZ LA LAO LB LBN LC LCA
LK LKA LR LBR LT LTU LU LUX LV LVA LY LBY MA MAR MD MDA ME MNE MG MDG MK MKD ML MLI MM MMR MN MNG MO MAC MR MRT MS MSR MT MLT
MU MUS MV MDV MW MWI MX MEX MY MYS MZ MOZ NA NAM NE NER NG NGA NI NIC NL NLD NO NOR NP NPL NR NRU NZ NZL OM OMN PA PAN PE PER
PG PNG PH PHL PK PAK PL POL PT PRT PW PLW PY PRY QA QAT RO ROU RS SRB RU RUS RW RWA SA SAU SB SLB SC SYC SE SWE SG SGP SI SVN
SK SVK SL SLE SN SEN SR SUR ST STP SV SLV SZ SWZ TC TCA TD TCD TH THA TJ TJK TM TKM TN TUN TO TON TR TUR TT TTO TW TWN TZ TZA
UA UKR UG UGA US USA UY URY UZ UZB VC VCT VE VEN VG VGB VN VNM VU VUT XK XKS YE YEM ZA ZAF ZM ZMB ZW ZWE`.split(/\s+/);
export const ALPHA3: Record<string, string> = Object.fromEntries(Array.from({ length: A3.length / 2 }, (_, i) => [A3[2 * i]!, A3[2 * i + 1]!]));
export function appleTerritory(o: PriceOverride): string {
const t = o.territory ?? ALPHA3[o.region];
if (!t) throw new Error(`no App Store territory known for region ${o.region}; set "territory" (alpha-3) on the override`);
return t;
}

68
cli/play/client.ts Normal file
View file

@ -0,0 +1,68 @@
// Google Play Developer API (androidpublisher v3) client: JSON + media calls with retry, and the edit lifecycle.
import { playServiceAccount } from '../lib/credentials';
import { tokenSource } from '../lib/google-auth';
import { log } from '../lib/log';
export type PlayClient = Awaited<ReturnType<typeof playClient>>;
export async function playClient(root: string, packageName: string) {
const token = tokenSource(playServiceAccount(root));
const API = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}`;
const UPLOAD = `https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${packageName}`;
/** JSON call (object body) or media upload (Uint8Array body + Content-Type). Throws with the API's text. */
async function call<T = unknown>(method: string, url: string, body?: unknown, headers: Record<string, string> = {}): Promise<T> {
const bin = body instanceof Uint8Array;
for (let attempt = 0; ; attempt++) {
const r = await fetch(url, {
method,
headers: { Authorization: 'Bearer ' + (await token()), ...(body === undefined || bin ? {} : { 'Content-Type': 'application/json' }), ...headers },
body: body === undefined ? undefined : bin ? (body as unknown as BodyInit) : JSON.stringify(body),
});
const text = await r.text();
if (r.ok) return (text ? JSON.parse(text) : {}) as T;
if ((r.status === 429 || r.status >= 500) && attempt < 4) { await Bun.sleep(2000 * (attempt + 1)); continue; }
const short = url.replace(API, '').replace(UPLOAD, '');
// A wrong path casing answers with an HTML 404 page, not JSON: say so instead of dumping the HTML.
const msg = /^\s*</.test(text) ? `(HTML ${r.status} page: wrong path or casing?)` : text.slice(0, 1500);
throw new Error(`${method} ${short} -> ${r.status} ${msg}`);
}
}
async function editOnce<T>(fn: (id: string) => Promise<T>, o: { dry?: boolean; commitQuery?: string }): Promise<T> {
const e = await call<{ id: string }>('POST', `${API}/edits`, {});
log('play', `edit ${e.id} opened`);
try {
const out = await fn(e.id);
if (o.dry) {
await call('POST', `${API}/edits/${e.id}:validate`);
log('play', 'dry run: edit validated, discarding');
await call('DELETE', `${API}/edits/${e.id}`);
} else {
await call('POST', `${API}/edits/${e.id}:commit${o.commitQuery ?? ''}`);
log('play', `edit ${e.id} committed`);
}
return out;
} catch (err) {
await call('DELETE', `${API}/edits/${e.id}`).catch(() => {});
throw err;
}
}
return {
API, UPLOAD, packageName, call,
/**
* Opens an edit, runs fn, then commits (or validates and discards when dry). Any Console save or other edit commit
* invalidates an open edit ("A change was made to the application outside of this Edit"); then the whole edit is
* redone, up to `retries` times. Keep edits small (e.g. one per language for images) so a retry is cheap.
*/
async edit<T>(fn: (id: string) => Promise<T>, o: { dry?: boolean; commitQuery?: string; retries?: number } = {}): Promise<T> {
for (let left = o.retries ?? 3; ; left--) {
try { return await editOnce(fn, o); } catch (err) {
if (left <= 0 || !/outside of this Edit|Edit has been deleted|edit.*(expired|not found)/i.test(String(err))) throw err;
log('play', `app changed outside the edit (Console?), redoing it (${left} retries left)`);
}
}
},
};
}

22
cli/play/data-safety.ts Normal file
View file

@ -0,0 +1,22 @@
// Play Data safety form through applications.dataSafety (CSV import; replaces every earlier answer).
// crt-ship play data-safety [--csv out.csv] [--dry]
// Answers come from `dataSafety` in crt-ship.config.ts. --dry only writes / prints the CSV.
import { writeFileSync } from 'node:fs';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { dataSafetyCsv } from '../lib/data-safety';
import { done, log } from '../lib/log';
import { playClient } from './client';
export async function run(ctx: Ctx): Promise<void> {
const { config, root, resolve } = ctx.loaded;
const csv = dataSafetyCsv(need(config.dataSafety, 'dataSafety'));
const out = ctx.args.get('csv');
if (out) { writeFileSync(resolve(out), csv); log('play', `wrote ${out}`); }
const types = Object.keys(config.dataSafety!.data);
log('play', `data safety: ${types.length} data types declared (${types.join(', ') || 'none'})`);
if (ctx.args.has('dry')) { if (!out) process.stdout.write(csv); return; }
const play = await playClient(root, need(config.play?.packageName, 'play.packageName'));
await play.call('POST', `${play.API}/dataSafety`, { safetyLabels: csv });
done('play', `data safety updated (${csv.split('\n').length - 2} rows)`);
}

72
cli/play/images.ts Normal file
View file

@ -0,0 +1,72 @@
// Play store graphics per language, one edit per language (a Console save in parallel then only costs a retry of that
// language, not of the whole upload).
// crt-ship play images [--dry] [--only en-US,de-DE] [--src store/renders]
// Per Play language, from <renders>/<language id>/: icon (play.icon, 512x512), featureGraphic (feature/1024x500.*),
// phoneScreenshots (play/NN.*), sevenInchScreenshots (tab7/NN.*), tenInchScreenshots (tab10/NN.*). A .jpg next to a
// .png wins (JPEG q92 uploads about 4x faster; `crt-ship shots jpeg` makes them). Each image type is cleared
// before uploading, so re-runs are idempotent. Uploads are slow: run in the background with a log file.
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import type { Ctx } from '../lib/ctx';
import { byPlayCode, need } from '../lib/ctx';
import { done, log, step } from '../lib/log';
import { playClient } from './client';
import { readPlayCopy } from './listing';
export const PLAY_IMAGE_DIRS = { phoneScreenshots: 'play', sevenInchScreenshots: 'tab7', tenInchScreenshots: 'tab10' } as const;
/** NN.png / NN.jpg in a dir, sorted, JPEG preferred over PNG of the same number. */
export function numberedImages(dir: string): string[] {
if (!existsSync(dir)) return [];
const files = readdirSync(dir).filter(f => /^\d+\.(png|jpe?g)$/i.test(f));
const byNum = new Map<string, string>();
for (const f of files.sort()) {
const n = f.split('.')[0]!;
const cur = byNum.get(n);
if (!cur || /\.jpe?g$/i.test(f)) byNum.set(n, f);
}
return [...byNum.keys()].sort((a, b) => Number(a) - Number(b)).map(n => join(dir, byNum.get(n)!));
}
export async function run(ctx: Ctx): Promise<void> {
const { config, root, resolve } = ctx.loaded;
const pkg = need(config.play?.packageName, 'play.packageName');
const src = resolve(ctx.args.get('src') ?? config.renders ?? config.shots?.out ?? 'store/renders');
const icon = resolve(config.play?.icon ?? join(config.icons?.out ?? 'store/icons', 'play-512.png'));
const langOf = byPlayCode(config.languages);
const codes = readPlayCopy(ctx).map(c => c.lang);
const sets = new Map<string, [string, string[]][]>();
for (const code of codes) {
const l = langOf.get(code);
if (!l) throw new Error(`${code}: no entry in languages[] has this Play code`);
const dir = join(src, l.id);
const feature = ['jpg', 'jpeg', 'png'].map(e => join(dir, `feature/1024x500.${e}`)).find(existsSync);
const set: [string, string[]][] = [['icon', [icon]], ['featureGraphic', feature ? [feature] : []],
...Object.entries(PLAY_IMAGE_DIRS).map(([type, sub]) => [type, numberedImages(join(dir, sub)).slice(0, 8)] as [string, string[]])];
for (const [type, files] of set) {
if (!files.length || !files.every(existsSync)) throw new Error(`${code}: no ${type} in ${dir} (render first: crt-ship shots render)`);
if (type.endsWith('Screenshots') && files.length < 2) throw new Error(`${code}: ${type} needs at least 2 images`);
}
sets.set(code, set);
}
log('play', `${codes.length} languages, ${[...sets.values()][0]?.reduce((n, [, f]) => n + f.length, 0) ?? 0} images each`);
const play = await playClient(root, pkg);
for (const [i, code] of codes.entries()) {
const set = sets.get(code)!;
await play.edit(async id => {
let n = 0;
await Promise.all(set.map(async ([type, files]) => {
await play.call('DELETE', `${play.API}/edits/${id}/listings/${code}/${type}`);
for (const f of files) {
await play.call('POST', `${play.UPLOAD}/edits/${id}/listings/${code}/${type}?uploadType=media`, new Uint8Array(readFileSync(f)),
{ 'Content-Type': /\.png$/i.test(f) ? 'image/png' : 'image/jpeg' });
n++;
}
}));
log('play', `${step(i + 1, codes.length, code)}: ${n} images`);
}, { dry: ctx.args.has('dry') });
}
done('play', 'images done');
}

36
cli/play/listing.ts Normal file
View file

@ -0,0 +1,36 @@
// Play store listing text + app details in one edit.
// crt-ship play listing [--dry] [--only en-US,de-DE]
// App details: default language, contact email, website (must be the domain that serves app-ads.txt).
// Listings: title, short and full description of every `<!-- play: ... -->` section of the listing file.
import { readFileSync } from 'node:fs';
import { parsePlayListing } from '../lib/copy';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log, step } from '../lib/log';
import { playClient } from './client';
export function readPlayCopy(ctx: Ctx) {
const { config, resolve } = ctx.loaded;
const file = resolve(config.play?.listing ?? 'store/listing.md');
const only = ctx.args.list('only');
return parsePlayListing(readFileSync(file, 'utf8'), config.app.name).filter(c => !only.length || only.includes(c.lang));
}
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const pkg = need(config.play?.packageName, 'play.packageName');
const copy = readPlayCopy(ctx);
const details = { defaultLanguage: config.play?.defaultLanguage ?? 'en-US', contactEmail: config.app.contactEmail, contactWebsite: config.app.website };
if (!copy.some(c => c.lang === details.defaultLanguage) && !ctx.args.has('only')) throw new Error(`listing has no section for the default language ${details.defaultLanguage}`);
log('play', `${copy.length} listings: ${copy.map(c => c.lang).join(', ')}`);
const play = await playClient(root, pkg);
await play.edit(async id => {
await play.call('PUT', `${play.API}/edits/${id}/details`, details);
log('play', `details: ${details.defaultLanguage} · ${details.contactEmail} · ${details.contactWebsite}`);
for (const [i, c] of copy.entries()) {
await play.call('PUT', `${play.API}/edits/${id}/listings/${c.lang}`, { language: c.lang, title: c.title, shortDescription: c.short, fullDescription: c.full });
log('play', step(i + 1, copy.length, c.lang));
}
}, { dry: ctx.args.has('dry') });
done('play', 'listing done');
}

77
cli/play/products.ts Normal file
View file

@ -0,0 +1,77 @@
// In-app products on Google Play through monetization.onetimeproducts (the legacy inappproducts API answers 403
// "Please migrate to the new publishing API" for apps on the new model).
// crt-ship play products [--dry] [--only id1,id2]
// Per product: convertRegionPrices (USD base -> every region at local price points, plus regionsVersion), your
// explicit overrides on top, PATCH ...?allowMissing=true (create or update), then activate the purchase option
// (new options start as DRAFT) with purchaseOptions:batchUpdateStates. Idempotent.
import { playCodes } from '../lib/config';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log, step, warn } from '../lib/log';
import { type Money, playRegionConfigs, toMoney } from '../lib/money';
import { playClient } from './client';
const OPTION = 'buy';
interface Converted {
convertedRegionPrices?: Record<string, { regionCode: string; price: Money }>;
convertedOtherRegionsPrice?: { usdPrice: Money; eurPrice: Money };
regionVersion?: { version: string };
}
interface Existing { productId: string; purchaseOptions?: { purchaseOptionId: string; state?: string }[] }
export async function listOneTimeProducts(play: Awaited<ReturnType<typeof playClient>>): Promise<Existing[]> {
const out: Existing[] = [];
let page = '';
do {
// camelCase "oneTimeProducts" here; the lowercase path answers with an HTML 404
const r = await play.call<{ oneTimeProducts?: Existing[]; nextPageToken?: string }>('GET', `${play.API}/oneTimeProducts?pageSize=100${page ? '&pageToken=' + page : ''}`);
out.push(...(r.oneTimeProducts ?? []));
page = r.nextPageToken ?? '';
} while (page);
return out;
}
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const pkg = need(config.play?.packageName, 'play.packageName');
const only = ctx.args.list('only');
const products = need(config.products, 'products').filter(p => !only.length || only.includes(p.id));
const dry = ctx.args.has('dry');
const play = await playClient(root, pkg);
const existing = new Map((await listOneTimeProducts(play)).map(p => [p.productId, p]));
const langs = new Map(config.languages.map(l => [l.id, l]));
for (const [i, p] of products.entries()) {
const tag = step(i + 1, products.length, p.id);
const conv = await play.call<Converted>('POST', `${play.API}/pricing:convertRegionPrices`, { price: toMoney('USD', p.usd) });
if (!conv.convertedRegionPrices || !conv.convertedOtherRegionsPrice) throw new Error(`${p.id}: convertRegionPrices returned no prices`);
const regions = playRegionConfigs(conv.convertedRegionPrices, p.overrides);
const listings = Object.entries(p.listings).flatMap(([id, l]) =>
playCodes(langs.get(id)!).map(languageCode => ({ languageCode, title: l.title, description: l.description })));
if (!listings.length) throw new Error(`${p.id}: no listing language has a Play code`);
const body = {
packageName: pkg, productId: p.id, listings,
purchaseOptions: [{
purchaseOptionId: OPTION,
buyOption: { legacyCompatible: true, multiQuantityEnabled: false },
regionalPricingAndAvailabilityConfigs: regions,
newRegionsConfig: { usdPrice: conv.convertedOtherRegionsPrice.usdPrice, eurPrice: conv.convertedOtherRegionsPrice.eurPrice, availability: 'AVAILABLE' },
}],
};
const cur = existing.get(p.id);
if (dry) { log('play', `${tag}: ${cur ? 'update' : 'create'} · ${regions.length} regions · ${listings.length} listings (dry)`); continue; }
const v = encodeURIComponent(conv.regionVersion?.version ?? '2022/02');
await play.call('PATCH', `${play.API}/onetimeproducts/${encodeURIComponent(p.id)}?allowMissing=true&regionsVersion.version=${v}&updateMask=listings,purchaseOptions`, body);
log('play', `${tag}: ${cur ? 'updated' : 'created'} · ${regions.length} regions · ${listings.length} listings`);
const active = cur?.purchaseOptions?.find(o => o.purchaseOptionId === OPTION)?.state === 'ACTIVE';
if (active) continue;
try {
await play.call('POST', `${play.API}/oneTimeProducts/${encodeURIComponent(p.id)}/purchaseOptions:batchUpdateStates`, {
requests: [{ activatePurchaseOptionRequest: { packageName: pkg, productId: p.id, purchaseOptionId: OPTION } }],
});
log('play', `${tag}: purchase option activated`);
} catch (e) { warn('play', `${tag}: activate failed: ${(e as Error).message}`); }
}
done('play', dry ? 'products checked (dry run, nothing written)' : 'products done');
}

60
cli/play/release.ts Normal file
View file

@ -0,0 +1,60 @@
// Upload an AAB (or reuse an uploaded versionCode) and put it on one or more tracks in one edit, with the "What's new"
// of every listing language as release notes.
// crt-ship play release app-release.aab [--internal completed] [--production draft] [--name "1.0.0 (3)"] [--dry]
// crt-ship play release --version-code 3 --production draft
// Tracks: internal, alpha (closed), beta (open), production; status completed | draft | inProgress | halted.
// Without a track flag: internal, completed. A production release that is not a draft asks for confirmation.
// Draft app (never published): Play only accepts status "draft" on any track; the first production release is sent
// from the Console (see the play-first-release skill).
import { readFileSync, statSync } from 'node:fs';
import type { Ctx } from '../lib/ctx';
import { confirm, need } from '../lib/ctx';
import { done, log } from '../lib/log';
import { playClient } from './client';
import { readPlayCopy } from './listing';
const TRACKS = ['internal', 'alpha', 'beta', 'production'];
const STATUSES = ['completed', 'draft', 'inProgress', 'halted'];
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const pkg = need(config.play?.packageName, 'play.packageName');
const a = ctx.args;
const aab = a._.find(x => x.endsWith('.aab'));
const fixed = a.get('version-code');
if (!aab && !fixed) throw new Error('usage: play release <app.aab> | --version-code N [--internal completed] [--production draft] [--name text] [--dry]');
const tracks = TRACKS.filter(t => a.has(t)).map(t => ({ track: t, status: a.get(t) === 'true' ? 'draft' : a.get(t)! }));
if (!tracks.length) tracks.push({ track: 'internal', status: 'completed' });
for (const t of tracks) if (!STATUSES.includes(t.status)) throw new Error(`--${t.track}: status must be one of ${STATUSES.join(', ')}`);
const fraction = a.get('fraction');
const live = tracks.find(t => t.track === 'production' && t.status !== 'draft');
if (live && !a.has('dry') && !(await confirm(ctx, `Release to PRODUCTION as "${live.status}" (public once Google approves)?`))) return;
const notes = readPlayCopy(ctx).filter(c => c.whatsNew).map(c => ({ language: c.lang, text: c.whatsNew }));
const play = await playClient(root, pkg);
try {
await play.edit(async id => {
let code = fixed ?? '';
if (aab) {
log('play', `uploading ${aab} (${(statSync(aab).size / 1048576).toFixed(1)} MB), this can take minutes`);
const b = await play.call<{ versionCode: number }>('POST', `${play.UPLOAD}/edits/${id}/bundles?uploadType=media`,
new Uint8Array(readFileSync(aab)), { 'Content-Type': 'application/octet-stream' });
code = String(b.versionCode);
log('play', `uploaded versionCode ${code}`);
}
for (const t of tracks) {
const release: Record<string, unknown> = { versionCodes: [code], status: t.status };
if (notes.length) release.releaseNotes = notes;
if (a.get('name')) release.name = a.get('name');
if (t.status === 'inProgress') release.userFraction = Number(fraction ?? 0.1);
await play.call('PUT', `${play.API}/edits/${id}/tracks/${t.track}`, { track: t.track, releases: [release] });
log('play', `${t.track} <- ${code} (${t.status}, notes in ${notes.length} languages)`);
}
}, { dry: a.has('dry'), retries: aab ? 0 : 3 });
} catch (e) {
if (/draft app|status draft/i.test(String(e)))
throw new Error(`${(e as Error).message}\n\nThis app has never been published: the API may only create DRAFT releases. Re-run with --production draft, then send it from the Console (skill play-first-release).`);
throw e;
}
done('play', 'release done');
}

31
cli/play/status.ts Normal file
View file

@ -0,0 +1,31 @@
// Read-only overview: tracks and releases, listing languages, one-time products and their purchase option states.
// crt-ship play status [--json]
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { playClient } from './client';
import { listOneTimeProducts } from './products';
interface Track { track: string; releases?: { name?: string; versionCodes?: string[]; status: string; userFraction?: number }[] }
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const play = await playClient(root, need(config.play?.packageName, 'play.packageName'));
const e = await play.call<{ id: string }>('POST', `${play.API}/edits`, {});
try {
const tracks = (await play.call<{ tracks?: Track[] }>('GET', `${play.API}/edits/${e.id}/tracks`)).tracks ?? [];
const listings = (await play.call<{ listings?: { language: string }[] }>('GET', `${play.API}/edits/${e.id}/listings`)).listings ?? [];
const details = await play.call<Record<string, string>>('GET', `${play.API}/edits/${e.id}/details`);
const products = await listOneTimeProducts(play);
if (ctx.args.has('json')) { console.log(JSON.stringify({ details, tracks, listings, products }, null, 2)); return; }
console.log(`${play.packageName}\n details: ${details.defaultLanguage} · ${details.contactEmail ?? '-'} · ${details.contactWebsite ?? '-'}`);
console.log(` listings (${listings.length}): ${listings.map(l => l.language).join(', ') || '-'}`);
for (const t of tracks) {
const rs = (t.releases ?? []).map(r => `${r.name ?? '?'} [${r.versionCodes?.join(',') ?? '-'}] ${r.status}${r.userFraction ? ' ' + r.userFraction * 100 + '%' : ''}`);
console.log(` track ${t.track.padEnd(11)} ${rs.join(' | ') || '-'}`);
}
console.log(` products (${products.length}):`);
for (const p of products) console.log(` ${p.productId.padEnd(40)} ${(p.purchaseOptions ?? []).map(o => `${o.purchaseOptionId}:${o.state ?? '?'}`).join(' ')}`);
} finally {
await play.call('DELETE', `${play.API}/edits/${e.id}`).catch(() => {});
}
}

23
cli/shots/browser.ts Normal file
View file

@ -0,0 +1,23 @@
// Playwright is not a dependency of crt-ship: it is resolved from the project (or the crt-ship folder), so a project
// that never renders screenshots never downloads a browser. Launch order: Playwright's own Chromium, then an installed
// Google Chrome (channel "chrome"), so `bunx playwright install chromium` is optional when Chrome is there.
import { join } from 'node:path';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function chromium(root: string): Promise<any> {
for (const base of [root, join(import.meta.dir, '../..')]) {
try {
const p = Bun.resolveSync('playwright', base);
return (await import(p)).chromium;
} catch { /* next */ }
}
throw new Error('playwright not found: in your project run `bun add -d playwright` (and `bunx playwright install chromium` if Google Chrome is not installed)');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function launch(root: string, opts: Record<string, unknown>): Promise<any> {
const c = await chromium(root);
try { return await c.launch(opts); } catch (e) {
try { return await c.launch({ ...opts, channel: 'chrome' }); } catch { throw e; }
}
}

53
cli/shots/capture.ts Normal file
View file

@ -0,0 +1,53 @@
// Raw frames from a running build of your game (headless Chromium, mobile viewport, touch, device scale).
// crt-ship shots capture [--lang en,tr] [--only hero,shop] [--device phone|tablet]
// Each entry of shots.capture.shots: { path, waitFor?: JS expression that turns true, eval?: [JS], delayMs?, device? }.
// URL = baseUrl + path + (? or &) + query, with "{lang}" replaced. Frames go to <shots.frames>/<lang>/<device>/<name>.png.
// The frame is the screen BELOW the status bar (the template draws one), so the viewport is phone.w x phone.h.
// Tip: give your game URL switches for screenshots (fixed seed, no ads, no tutorial, freeze at a moment).
import { mkdirSync } from 'node:fs';
import { join } from 'node:path';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log, warn } from '../lib/log';
import { launch } from './browser';
import { DEFAULT_PHONE, DEFAULT_TABLET } from './lib';
export async function run(ctx: Ctx): Promise<void> {
const { config, root, resolve } = ctx.loaded;
const shots = need(config.shots, 'shots');
const cap = need(shots.capture, 'shots.capture');
const a = ctx.args;
const langs = a.list('lang').length ? a.list('lang') : config.languages.map(l => l.id);
const only = a.list('only');
const devices = a.get('device') ? [a.get('device') as 'phone' | 'tablet'] : (['phone', 'tablet'] as const);
const dims = { phone: { ...DEFAULT_PHONE, ...shots.phone }, tablet: { ...DEFAULT_TABLET, ...shots.tablet } };
const browser = await launch(root, { headless: true, args: ['--mute-audio', '--ignore-gpu-blocklist', '--enable-webgl', '--enable-gpu'] });
let failed = 0;
try {
for (const lang of langs) for (const device of devices) for (const [name, s] of Object.entries(cap.shots)) {
if (only.length && !only.includes(name)) continue;
const want = s.device ?? 'phone';
if (want !== 'both' && want !== device) continue;
const d = dims[device];
const out = resolve(join(shots.frames, lang, device));
mkdirSync(out, { recursive: true });
const q = (cap.query ?? '').replaceAll('{lang}', lang);
const url = cap.baseUrl.replace(/\/$/, '') + s.path.replaceAll('{lang}', lang) + (q ? (s.path.includes('?') ? '&' : '?') + q : '');
const c = await browser.newContext({ viewport: { width: d.w, height: d.h }, deviceScaleFactor: d.dpr, hasTouch: true, isMobile: true, serviceWorkers: 'block', locale: lang });
const page = await c.newPage();
const errs: string[] = [];
page.on('pageerror', (e: Error) => errs.push(e.message));
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 90000 });
if (s.waitFor) await page.waitForFunction(s.waitFor, null, { timeout: 120000, polling: 50 });
for (const js of s.eval ?? []) await page.evaluate(js);
await page.waitForTimeout(s.delayMs ?? 1000);
await page.screenshot({ path: join(out, `${name}.png`) });
log('shots', `${lang}/${device}/${name}.png${errs.length ? ' (page errors: ' + errs.join(' | ').slice(0, 200) + ')' : ''}`);
} catch (e) { failed++; warn('shots', `${lang}/${device}/${name}: ${(e as Error).message.slice(0, 300)}`); }
await c.close();
}
} finally { await browser.close(); }
if (failed) throw new Error(`${failed} captures failed`);
done('shots', 'capture done');
}

63
cli/shots/feature.html Normal file
View file

@ -0,0 +1,63 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/fonts/fonts.css">
<!-- Play feature graphic, 1024 x 500: logo (or the app name) on the left, a tilted phone with one frame on the right.
feature.html?lang=<id>. Rendered by `crt-ship shots render`. Keep text out of it: Play overlays its own UI. -->
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 1024px; height: 500px; overflow: hidden; background: #000; }
#c { position: relative; width: 1024px; height: 500px; overflow: hidden; isolation: isolate; background: var(--bg); }
.rays { position: absolute; left: 360px; top: 250px; width: 1700px; height: 1700px; transform: translate(-50%, -50%) rotate(8deg); z-index: -4;
opacity: .55; background: repeating-conic-gradient(rgba(255,255,255,.22) 0deg 7deg, rgba(255,255,255,0) 7deg 18deg);
-webkit-mask-image: radial-gradient(closest-side, #000 0%, rgba(0,0,0,.6) 30%, transparent 75%); }
.glow { position: absolute; left: 360px; top: 250px; width: 900px; height: 700px; transform: translate(-50%, -50%); z-index: -3;
background: radial-gradient(closest-side, var(--glow), transparent); }
.vign { position: absolute; inset: 0; z-index: -2; background: radial-gradient(90% 90% at 45% 50%, transparent 60%, rgba(0,0,0,.28) 100%); }
.logo { position: absolute; left: 360px; top: 252px; width: 600px; transform: translate(-50%, -50%) rotate(-3deg); z-index: 5;
filter: drop-shadow(0 14px 22px rgba(0,0,0,.4)); }
.name { position: absolute; left: 360px; top: 250px; transform: translate(-50%, -50%) rotate(-3deg); z-index: 5; width: 640px; text-align: center;
font-family: var(--tf); font-size: 104px; line-height: .95; color: #fff; -webkit-text-stroke: 9px var(--ink); paint-order: stroke fill;
filter: drop-shadow(0 8px 0 var(--ink)) drop-shadow(0 14px 22px rgba(0,0,0,.35)); }
.tel { position: absolute; left: 748px; top: 36px; width: 232px; aspect-ratio: var(--ar); transform: rotate(7deg); z-index: 3;
border-radius: 40px; padding: 6px;
background: linear-gradient(140deg, #f4f4f6 0%, #9da0a8 8%, #3c3e44 20%, #1a1b1f 50%, #3c3e44 82%, #c8cad0 100%);
box-shadow: inset 0 0 0 1px rgba(255,255,255,.6), inset 0 0 0 3px #101114, 0 20px 50px var(--shadow); }
.screen { position: relative; width: 100%; height: 100%; overflow: hidden; border-radius: 34px; background: #000; }
.screen img { position: absolute; left: 0; bottom: 0; width: 100%; height: var(--appH); object-fit: cover; object-position: top; }
.screen .bar { position: absolute; left: 0; right: 0; top: 0; height: var(--barH); background: #0b0b10 center top / 100% 60000% no-repeat; }
.island { position: absolute; top: 6px; left: 50%; transform: translateX(-50%); width: 30%; height: 20px; background: #000; border-radius: 99px; }
.spark { position: absolute; width: var(--s); height: var(--s); transform: translate(-50%, -50%); background: #fff; z-index: -1;
clip-path: polygon(50% 0, 58% 42%, 100% 50%, 58% 58%, 50% 100%, 42% 58%, 0 50%, 42% 42%); opacity: var(--o); filter: drop-shadow(0 0 6px #fff); }
</style>
</head>
<body>
<div id="c">
<div class="glow"></div><div class="rays"></div><div class="vign"></div>
<div class="tel"><div class="screen"><div class="bar" id="bar"></div><img id="f"><div class="island"></div></div></div>
</div>
<script type="module">
const lang = new URLSearchParams(location.search).get('lang');
const job = await (await fetch(`/job.json?lang=${encodeURIComponent(lang)}&feature=1`)).json();
const P = job.palette, R = document.documentElement.style, dev = job.phone;
R.setProperty('--bg', `linear-gradient(160deg, ${P.bg[0]} 0%, ${P.bg[1]} 50%, ${P.bg[2]} 100%)`);
R.setProperty('--glow', P.glow); R.setProperty('--ink', P.ink); R.setProperty('--shadow', P.shadow);
R.setProperty('--tf', `'${job.fonts.title}', sans-serif`);
R.setProperty('--ar', `${dev.w} / ${dev.h + dev.bar}`);
R.setProperty('--appH', `${100 * dev.h / (dev.h + dev.bar)}%`);
R.setProperty('--barH', `${100 * dev.bar / (dev.h + dev.bar)}%`);
const c = document.getElementById('c');
const src = `/frames/${encodeURIComponent(lang)}/phone/${encodeURIComponent(job.frame)}`;
document.getElementById('f').src = src;
document.getElementById('bar').style.backgroundImage = `url('${src}')`;
c.insertAdjacentHTML('beforeend', job.logo ? '<img class="logo" src="/logo">' : `<div class="name">${String(job.appName).replace(/</g, '&lt;')}</div>`);
let seed = 7; const rnd = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296);
for (let i = 0; i < 22; i++)
c.insertAdjacentHTML('beforeend', `<i class="spark" style="left:${rnd() * 1024}px;top:${rnd() * 500}px;--s:${8 + rnd() * 18}px;--o:${.5 + rnd() * .5}"></i>`);
await document.fonts.ready;
await Promise.all([...document.images].map(i => i.decode().catch(() => {})));
document.body.dataset.ready = '1';
</script>
</body>
</html>

61
cli/shots/fonts.ts Normal file
View file

@ -0,0 +1,61 @@
// Downloads the fonts the screenshot template needs, subset by Google Fonts (css2 `text=`) to exactly the characters
// your copy uses, so even CJK stays small: title + body family for Latin text, plus one Noto family per non-Latin
// script found in the copy (CJK, Arabic, Hebrew, Devanagari, Thai, Cyrillic / Greek).
// crt-ship shots fonts (re-run after editing the copy)
// Out: <project>/.crt-ship/fonts/*.woff2 + fonts.css. All of these fonts are SIL Open Font License 1.1.
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log } from '../lib/log';
import { type CopyFile, copyText, scriptFont } from './lib';
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36';
const ASCII = Array.from({ length: 95 }, (_, i) => String.fromCharCode(32 + i)).join('') + '·';
async function fetchSubset(family: string, chars: string, variable: boolean): Promise<ArrayBuffer> {
const fam = encodeURIComponent(family) + (variable ? ':wght@100..900' : '');
let res = await fetch(`https://fonts.googleapis.com/css2?family=${fam}&text=${encodeURIComponent(chars)}&display=block`, { headers: { 'user-agent': UA } });
if (!res.ok && variable) return fetchSubset(family, chars, false);
if (!res.ok) throw new Error(`${family}: ${res.status} ${await res.text()}`);
const src = /url\((https:[^)]+)\)/.exec(await res.text())?.[1];
if (!src) throw new Error(`${family}: no font url`);
res = await fetch(src, { headers: { 'user-agent': UA } });
return res.arrayBuffer();
}
export async function run(ctx: Ctx): Promise<void> {
const { config, root, resolve } = ctx.loaded;
const shots = need(config.shots, 'shots');
const copy = JSON.parse(readFileSync(resolve(shots.copy), 'utf8')) as CopyFile;
const dir = join(root, '.crt-ship', 'fonts');
mkdirSync(dir, { recursive: true });
const title = shots.fonts?.title ?? 'Lilita One', body = shots.fonts?.body ?? 'Montserrat';
const latin = new Set(ASCII);
const scripts = new Map<string, { family: string; chars: Set<string> }>();
for (const [lang, c] of Object.entries(copy)) {
const text = copyText(lang, c);
const s = scriptFont(lang, text);
if (!s) { for (const ch of text) latin.add(ch); continue; }
const cur = scripts.get(s.slug) ?? { family: s.family, chars: new Set(ASCII) };
for (const ch of text) cur.chars.add(ch);
scripts.set(s.slug, cur);
}
for (const ch of config.app.name) latin.add(ch);
const latinChars = [...latin].sort().join('');
let css = '/* Generated by crt-ship shots fonts (Google Fonts, SIL Open Font License 1.1), subset to the copy. */\n';
const jobs: [string, string, string, boolean][] = [[title, 'title', latinChars, false], [body, 'body', latinChars, true],
...[...scripts.entries()].map(([slug, s]) => [s.family, slug, [...s.chars].sort().join(''), true] as [string, string, string, boolean])];
for (const [family, slug, chars, variable] of jobs) {
const bin = await fetchSubset(family, chars, variable);
const file = `${slug}.woff2`;
writeFileSync(join(dir, file), Buffer.from(bin));
const face = slug === 'title' ? title : slug === 'body' ? body : `Store ${slug}`;
css += `@font-face { font-family: '${face}'; font-weight: 100 900; src: url(${file}) format('woff2'); }\n`;
log('shots', `${file.padEnd(18)} ${family.padEnd(22)} ${(bin.byteLength / 1024).toFixed(1)} KB, ${[...chars].length} chars`);
}
writeFileSync(join(dir, 'fonts.css'), css);
done('shots', 'fonts done');
}

34
cli/shots/jpeg.ts Normal file
View file

@ -0,0 +1,34 @@
// JPEG copies of the Play renders (play, tab7, tab10, feature): about 4x smaller, so `play images` uploads much faster.
// The App Store screenshots stay PNG.
// crt-ship shots jpeg [--q 92] [--lang en,tr]
import { existsSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import type { Ctx } from '../lib/ctx';
import { done, log } from '../lib/log';
/** ffmpeg's mjpeg -q:v (2 best .. 31 worst) for a 0-100 quality. */
export const ffmpegQ = (q: number): number => Math.max(2, Math.min(31, Math.round(2 + (100 - q) * 0.29)));
export async function run(ctx: Ctx): Promise<void> {
const { config, resolve } = ctx.loaded;
const src = resolve(ctx.args.get('src') ?? config.renders ?? config.shots?.out ?? 'store/renders');
const q = ctx.args.num('q', 92);
const langs = ctx.args.list('lang').length ? ctx.args.list('lang') : config.languages.map(l => l.id);
const sips = spawnSync('sh', ['-c', 'command -v sips']).status === 0;
let n = 0;
for (const lang of langs) for (const sub of ['play', 'tab7', 'tab10', 'feature']) {
const dir = join(src, lang, sub);
if (!existsSync(dir)) continue;
for (const f of readdirSync(dir).filter(f => f.endsWith('.png'))) {
const inp = join(dir, f), out = inp.replace(/\.png$/, '.jpg');
const r = sips
? spawnSync('sips', ['-s', 'format', 'jpeg', '-s', 'formatOptions', String(q), inp, '--out', out])
: spawnSync('ffmpeg', ['-v', 'error', '-y', '-i', inp, '-q:v', String(ffmpegQ(q)), out]);
if (r.status !== 0) throw new Error(`jpeg failed for ${inp}`);
n++;
}
log('shots', `${lang}/${sub}: jpeg q${q}`);
}
done('shots', `${n} JPEG copies`);
}

76
cli/shots/lib.ts Normal file
View file

@ -0,0 +1,76 @@
// Pure parts of the screenshot pipeline: store sizes, palettes, per-language script fonts, copy validation, job data.
import type { Palette, ShotsConfig } from '../lib/config';
/** Store image sizes. ios69 = 6.9" (APP_IPHONE_67), ios65 = 6.5" (APP_IPHONE_65), play = Play phone 9:16,
* tab7 / tab10 = Play 7" / 10" tablets (tablet frames), feature = Play feature graphic. */
export const SIZES: Record<string, { w: number; h: number; device?: 'tablet'; feature?: true }> = {
ios69: { w: 1320, h: 2868 },
ios65: { w: 1284, h: 2778 },
play: { w: 1080, h: 1920 },
tab7: { w: 1080, h: 1920, device: 'tablet' },
tab10: { w: 1440, h: 2560, device: 'tablet' },
feature: { w: 1024, h: 500, feature: true },
};
export const PALETTES: Record<string, Palette> = {
fire: { bg: ['#ff3d2e', '#ff7a1a', '#ffc43a'], glow: 'rgba(255,240,150,.95)', ink: '#6e1300', shadow: 'rgba(120,10,0,.45)' },
violet: { bg: ['#2b1676', '#6a2fe0', '#ff5fb8'], glow: 'rgba(255,170,240,.75)', ink: '#22095e', shadow: 'rgba(30,0,80,.55)' },
blast: { bg: ['#ff2d55', '#ff6a2a', '#ffb02e'], glow: 'rgba(255,236,140,.9)', ink: '#6a0a1e', shadow: 'rgba(110,0,30,.45)' },
green: { bg: ['#0fa35c', '#3cc96a', '#ff6f91'], glow: 'rgba(230,255,170,.85)', ink: '#0a4a2a', shadow: 'rgba(0,60,30,.45)' },
night: { bg: ['#14105a', '#4a2bd6', '#11b8ff'], glow: 'rgba(140,230,255,.85)', ink: '#170d55', shadow: 'rgba(10,0,70,.55)' },
sky: { bg: ['#0a6fe0', '#21a8ff', '#7be3ff'], glow: 'rgba(255,255,255,.85)', ink: '#06336e', shadow: 'rgba(0,40,100,.45)' },
gold: { bg: ['#ff8a00', '#ffb300', '#ffe14a'], glow: 'rgba(255,255,220,.95)', ink: '#6b2c00', shadow: 'rgba(120,50,0,.4)' },
candy: { bg: ['#d01f7a', '#ff4f8b', '#ff9f43'], glow: 'rgba(255,220,180,.9)', ink: '#5e0636', shadow: 'rgba(90,0,50,.45)' },
};
export const palette = (p: string | Palette | undefined): Palette => {
if (!p) return PALETTES.fire!;
if (typeof p !== 'string') return p;
const hit = PALETTES[p];
if (!hit) throw new Error(`unknown palette "${p}" (${Object.keys(PALETTES).join(', ')}) or pass { bg, glow, ink, shadow }`);
return hit;
};
export interface SceneCopy { eyebrow?: string; title: [string, string]; sub?: string; badges?: [string, string?][] }
export type CopyFile = Record<string, Record<string, SceneCopy>>;
/** Noto family + slug for the first non-Latin script found in a language's text, or null for Latin-only text. */
export function scriptFont(lang: string, text: string): { family: string; slug: string } | null {
if (/\p{Script=Hangul}/u.test(text)) return { family: 'Noto Sans KR', slug: 'kr' };
if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(text)) return { family: 'Noto Sans JP', slug: 'jp' };
if (/\p{Script=Han}/u.test(text)) return /Hant|TW|HK/.test(lang) ? { family: 'Noto Sans TC', slug: 'tc' } : { family: 'Noto Sans SC', slug: 'sc' };
if (/\p{Script=Arabic}/u.test(text)) return { family: 'Noto Sans Arabic', slug: 'arabic' };
if (/\p{Script=Hebrew}/u.test(text)) return { family: 'Noto Sans Hebrew', slug: 'hebrew' };
if (/\p{Script=Devanagari}/u.test(text)) return { family: 'Noto Sans Devanagari', slug: 'devanagari' };
if (/\p{Script=Bengali}/u.test(text)) return { family: 'Noto Sans Bengali', slug: 'bengali' };
if (/\p{Script=Thai}/u.test(text)) return { family: 'Noto Sans Thai', slug: 'thai' };
if (/[\p{Script=Cyrillic}\p{Script=Greek}]/u.test(text)) return { family: 'Noto Sans', slug: 'cyrgreek' };
return null;
}
/** Every string of a language's copy (for font subsetting and script detection), upper-cased eyebrows included. */
export function copyText(lang: string, c: Record<string, SceneCopy> | undefined): string {
if (!c) return '';
return Object.values(c).flatMap(s => [s.eyebrow ?? '', (s.eyebrow ?? '').toLocaleUpperCase(lang), ...s.title, s.sub ?? '',
...(s.badges ?? []).flat().filter((x): x is string => !!x)]).join('');
}
/** Problems with the copy file for the configured scenes and languages. */
export function validateCopy(copy: CopyFile, shots: ShotsConfig, langs: string[]): string[] {
const e: string[] = [];
for (const lang of langs) {
const c = copy[lang];
if (!c) { e.push(`${lang}: no copy`); continue; }
for (const s of shots.scenes) {
const sc = c[s.id];
if (!sc) { e.push(`${lang} scene ${s.id}: missing`); continue; }
if (!Array.isArray(sc.title) || sc.title.length !== 2) e.push(`${lang} scene ${s.id}: title must be [line1, line2]`);
const slots = s.badges?.length ?? 0;
if ((sc.badges?.length ?? 0) > slots) e.push(`${lang} scene ${s.id}: ${sc.badges!.length} badges but the scene has ${slots} slots`);
}
}
return e;
}
export const DEFAULT_PHONE = { w: 440, h: 902, dpr: 3, bar: 54 };
export const DEFAULT_TABLET = { w: 800, h: 1240, dpr: 2, bar: 40 };

129
cli/shots/render.ts Normal file
View file

@ -0,0 +1,129 @@
// Store screenshots + Play feature graphic at exact pixel sizes, from raw game frames and per-language copy.
// crt-ship shots render [--lang en,tr] [--size ios69,play,feature] [--scenes 1,3] [--no-sheet] [--port 8792]
// Out: <shots.out>/<lang>/<size>/NN.png (24-bit RGB: Play rejects alpha / palette PNGs), <lang>/feature/1024x500.png,
// and <lang>/sheet.png (contact sheet of every size, for a quick review). Needs playwright + ffmpeg.
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
import { join, relative } from 'node:path';
import { spawnSync } from 'node:child_process';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
import { done, log, warn } from '../lib/log';
import { launch } from './browser';
import { type CopyFile, DEFAULT_PHONE, DEFAULT_TABLET, SIZES, copyText, palette, scriptFont, validateCopy } from './lib';
const TYPES: Record<string, string> = { html: 'text/html; charset=utf-8', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
webp: 'image/webp', woff2: 'font/woff2', css: 'text/css', svg: 'image/svg+xml' };
export async function run(ctx: Ctx): Promise<void> {
const { config, root, resolve } = ctx.loaded;
const shots = need(config.shots, 'shots');
const a = ctx.args;
const langs = a.list('lang').length ? a.list('lang') : config.languages.map(l => l.id);
const sizes = a.list('size').length ? a.list('size') : Object.keys(SIZES);
const sceneIds = a.list('scenes').length ? a.list('scenes') : shots.scenes.map(s => s.id);
for (const s of sizes) if (!SIZES[s]) throw new Error(`unknown size ${s} (${Object.keys(SIZES).join(', ')})`);
const copy = JSON.parse(readFileSync(resolve(shots.copy), 'utf8')) as CopyFile;
const problems = validateCopy(copy, shots, langs);
if (problems.length) throw new Error('shots copy:\n ' + problems.join('\n '));
const framesDir = resolve(shots.frames), outDir = resolve(shots.out);
const fontsDir = join(root, '.crt-ship', 'fonts');
if (!existsSync(join(fontsDir, 'fonts.css'))) warn('shots', 'no .crt-ship/fonts/fonts.css: run `crt-ship shots fonts` first (falling back to system fonts)');
const phone = { ...DEFAULT_PHONE, ...shots.phone }, tablet = { ...DEFAULT_TABLET, ...shots.tablet };
const rtl = new Map(config.languages.map(l => [l.id, !!l.rtl] as const));
const fonts = { title: shots.fonts?.title ?? 'Lilita One', body: shots.fonts?.body ?? 'Montserrat' };
const tpl = import.meta.dir;
const server = Bun.serve({
port: a.num('port', 8792), hostname: '127.0.0.1',
async fetch(req) {
const url = new URL(req.url);
const p = decodeURIComponent(url.pathname);
if (p === '/job.json') {
const lang = url.searchParams.get('lang') ?? '';
const text = copyText(lang, copy[lang]);
const script = scriptFont(lang, text);
const base = { phone, tablet, rtl: rtl.get(lang), logo: !!shots.logo, appName: config.app.name,
fonts: { ...fonts, script: script ? `Store ${script.slug}` : null } };
if (url.searchParams.has('feature'))
return Response.json({ ...base, frame: shots.feature?.frame ?? shots.scenes[0]!.frames[0]!.src, palette: palette(shots.feature?.palette ?? shots.scenes[0]!.palette) });
const sid = url.searchParams.get('s') ?? '';
const scene = shots.scenes.find(s => s.id === sid)!;
const number = shots.scenes.filter(s => !s.hero).indexOf(scene) + 1;
return Response.json({ ...base, scene, number, palette: palette(scene.palette), copy: copy[lang]?.[sid] ?? {} });
}
let file = '';
if (p === '/logo' && shots.logo) file = resolve(shots.logo);
else if (p.startsWith('/frames/')) file = join(framesDir, p.slice(8));
else if (p.startsWith('/fonts/')) file = join(fontsDir, p.slice(7));
else if (p.startsWith('/out/')) file = join(outDir, p.slice(5));
else if (/^\/(template|feature)\.html$/.test(p)) file = join(tpl, p.slice(1));
if (!file || file.includes('..')) return new Response('no', { status: 404 });
const f = Bun.file(file);
if (!(await f.exists())) return new Response('missing ' + p, { status: 404 });
return new Response(f, { headers: { 'content-type': TYPES[file.split('.').pop()!.toLowerCase()] ?? 'application/octet-stream' } });
},
});
const base = `http://127.0.0.1:${server.port}`;
const browser = await launch(root, { headless: true });
const page = await (await browser.newContext({ deviceScaleFactor: 1 })).newPage();
const errs: string[] = [];
page.on('pageerror', (e: Error) => errs.push(e.message));
page.on('response', (r: { status(): number; url(): string }) => { if (r.status() >= 400 && !r.url().includes('/fonts/')) errs.push(`${r.status()} ${r.url()}`); });
let warnings = 0;
async function shoot(url: string, w: number, h: number, out: string): Promise<void> {
errs.length = 0;
mkdirSync(join(out, '..'), { recursive: true });
await page.setViewportSize({ width: w, height: h });
await page.goto(url, { waitUntil: 'load' });
await page.waitForFunction("document.body.dataset.ready === '1'", null, { timeout: 30000 });
await page.waitForTimeout(200);
const tmp = out + '.tmp.png';
await page.screenshot({ path: tmp, clip: { x: 0, y: 0, width: w, height: h } });
const r = spawnSync('ffmpeg', ['-v', 'error', '-y', '-i', tmp, '-pix_fmt', 'rgb24', out]);
rmSync(tmp, { force: true });
if (r.status !== 0) throw new Error('ffmpeg: ' + r.stderr);
if (errs.length) warnings++;
log('shots', `${errs.length ? 'WARN ' : ''}${relative(root, out)}${errs.length ? ' ' + errs.join(' | ') : ''}`);
}
try {
for (const lang of langs) {
const rows: { size: string; files: string[] }[] = [];
for (const size of sizes) {
const s = SIZES[size]!;
if (s.feature) {
const f = join(outDir, lang, 'feature', '1024x500.png');
await shoot(`${base}/feature.html?lang=${encodeURIComponent(lang)}`, s.w, s.h, f);
rows.push({ size, files: [f] });
continue;
}
const files: string[] = [];
for (const [i, id] of sceneIds.entries()) {
const f = join(outDir, lang, size, `${String(i + 1).padStart(2, '0')}.png`);
await shoot(`${base}/template.html?s=${encodeURIComponent(id)}&w=${s.w}&h=${s.h}&lang=${encodeURIComponent(lang)}${s.device ? '&device=tablet' : ''}`, s.w, s.h, f);
files.push(f);
}
rows.push({ size, files });
}
if (!a.has('no-sheet') && rows.length) {
const H0 = 900, gap = 24;
let y = gap, W = 0, imgs = '';
for (const row of rows) {
const d = SIZES[row.size]!, h = d.feature ? H0 / 2 : H0, w = Math.round(h * d.w / d.h);
row.files.forEach((f, i) => { imgs += `<img src="/out/${relative(outDir, f)}" style="position:absolute;left:${gap + i * (w + gap)}px;top:${y}px;width:${w}px;height:${h}px;border-radius:14px">`; });
W = Math.max(W, gap + row.files.length * (w + gap)); y += h + gap;
}
const sheet = join(outDir, lang, '.sheet.html');
await Bun.write(sheet, `<!doctype html><body style="margin:0;background:#141418;width:${W}px;height:${y}px;position:relative">${imgs}
<script>Promise.all([...document.images].map(i=>i.decode())).then(()=>document.body.dataset.ready='1')</script></body>`);
await shoot(`${base}/out/${relative(outDir, sheet)}`, W, y, join(outDir, lang, 'sheet.png'));
rmSync(sheet, { force: true });
}
}
} finally {
await browser.close();
server.stop(true);
}
done('shots', `render done${warnings ? `, ${warnings} with warnings (missing frames or fonts?)` : ''}`);
}

231
cli/shots/template.html Normal file
View file

@ -0,0 +1,231 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/fonts/fonts.css">
<!--
Store screenshot template (rendered by `crt-ship shots render`, served by its local server).
template.html?s=<scene id>&w=<px>&h=<px>&lang=<id>[&device=tablet]
/job.json gives the scene (palette, frames, badge slots), the words for this language and the font families.
Layout: everything is in u = W / 100; the text block is measured first, then the phone fills the room below it.
-->
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { overflow: hidden; background: #000; }
body { font-family: var(--bf); -webkit-font-smoothing: antialiased; color: #fff; }
#c { position: relative; overflow: hidden; width: var(--W); height: var(--H); isolation: isolate; background: var(--bg); }
/* background: sunburst, glow, vignette, sparkles */
.rays { position: absolute; left: 50%; top: var(--rayY); width: calc(var(--u) * 260); height: calc(var(--u) * 260);
transform: translate(-50%, -50%) rotate(var(--rayRot, 0deg)); z-index: -4; opacity: .55;
background: repeating-conic-gradient(from 0deg, rgba(255,255,255,.20) 0deg 7deg, rgba(255,255,255,0) 7deg 18deg);
-webkit-mask-image: radial-gradient(closest-side, #000 0%, rgba(0,0,0,.65) 35%, transparent 80%); }
.glow { position: absolute; left: 50%; top: var(--rayY); width: calc(var(--u) * 150); height: calc(var(--u) * 150); z-index: -3;
transform: translate(-50%, -50%); background: radial-gradient(closest-side, var(--glow) 0%, transparent 100%); opacity: .9; }
.vign { position: absolute; inset: 0; z-index: -2; pointer-events: none;
background: radial-gradient(120% 70% at 50% 45%, transparent 55%, rgba(0,0,0,.28) 100%); }
.spark { position: absolute; z-index: -1; width: var(--s); height: var(--s); transform: translate(-50%, -50%) rotate(var(--r));
background: #fff; opacity: var(--o);
clip-path: polygon(50% 0, 58% 42%, 100% 50%, 58% 58%, 50% 100%, 42% 58%, 0 50%, 42% 42%);
filter: drop-shadow(0 0 calc(var(--u) * .8) rgba(255,255,255,.9)); }
.dot { position: absolute; z-index: -1; width: var(--s); height: var(--s); border-radius: 50%; transform: translate(-50%, -50%);
background: radial-gradient(circle, rgba(255,255,255,.55), rgba(255,255,255,0) 70%); }
/* text block */
.top { position: absolute; left: 0; right: 0; top: calc(var(--u) * var(--top)); text-align: center; z-index: 6;
padding: 0 calc(var(--u) * 5); display: flex; flex-direction: column; align-items: center; }
.eyebrow { display: inline-flex; align-items: center; gap: calc(var(--u) * 1.6); margin-bottom: calc(var(--u) * 3);
padding: calc(var(--u) * 1.15) calc(var(--u) * 3) calc(var(--u) * 1.15) calc(var(--u) * 1.3); border-radius: 999px;
background: rgba(0,0,0,.26); box-shadow: inset 0 0 0 calc(var(--u) * .25) rgba(255,255,255,.28);
font-size: calc(var(--u) * 2.75); font-weight: 800; letter-spacing: var(--kls, .2em); text-transform: uppercase; line-height: 1.15; }
.eyebrow b { display: grid; place-items: center; width: calc(var(--u) * 5); height: calc(var(--u) * 5); border-radius: 50%;
background: linear-gradient(180deg, #fff3a8, #ffc31f 55%, #ff9500); color: #5a2300; font-weight: 900; letter-spacing: 0;
font-size: calc(var(--u) * 2.5); box-shadow: 0 calc(var(--u) * .35) 0 rgba(0,0,0,.25); }
h1 { font-family: var(--tf); font-weight: var(--tw); line-height: var(--lh); letter-spacing: var(--ls, .005em);
font-size: calc(var(--u) * var(--fs)); color: #fff;
-webkit-text-stroke: calc(var(--u) * var(--fs) * var(--sk, .085)) var(--ink); paint-order: stroke fill;
filter: drop-shadow(0 calc(var(--u) * .9) 0 var(--ink)) drop-shadow(0 calc(var(--u) * 1.6) calc(var(--u) * 2.2) rgba(0,0,0,.3)); }
h1 span { display: block; width: max-content; margin: 0 auto; white-space: nowrap; }
h1 span + span { color: #ffd23a; font-size: .86em; margin-top: .06em; }
p.sub { margin-top: calc(var(--u) * 2.6); max-width: calc(var(--u) * 80); font-size: calc(var(--u) * var(--sub));
line-height: 1.32; font-weight: 700; color: #fff; text-wrap: balance;
text-shadow: 0 calc(var(--u) * .3) calc(var(--u) * 1) rgba(0,0,0,.35); }
.logo { width: calc(var(--u) * 86); max-height: calc(var(--u) * 36); object-fit: contain; margin: calc(var(--u) * -1) 0 calc(var(--u) * 1);
filter: drop-shadow(0 calc(var(--u) * 1.5) calc(var(--u) * 2.5) rgba(0,0,0,.35)); }
/* phone */
.tel { position: absolute; z-index: 3; width: var(--pw); aspect-ratio: var(--ar); left: var(--px); top: var(--py);
transform: translateX(-50%) rotate(var(--pr, 0deg)); transform-origin: 50% 40%;
border-radius: calc(var(--pw) * .16); padding: calc(var(--pw) * .024);
background: linear-gradient(140deg, #f4f4f6 0%, #9da0a8 8%, #3c3e44 20%, #1a1b1f 50%, #3c3e44 82%, #c8cad0 100%);
box-shadow: inset 0 0 0 calc(var(--pw) * .004) rgba(255,255,255,.6), inset 0 0 0 calc(var(--pw) * .011) #101114,
0 calc(var(--u) * 3) calc(var(--u) * 6) rgba(0,0,0,.35), 0 calc(var(--u) * 6) calc(var(--u) * 14) var(--shadow); }
.tel.back { z-index: 2; filter: saturate(.9) brightness(.9); }
.screen { position: relative; width: 100%; height: 100%; overflow: hidden; background: #000; border-radius: calc(var(--pw) * .137); }
.screen img.app { position: absolute; left: 0; bottom: 0; width: 100%; height: var(--appH); object-fit: cover; object-position: top; }
.screen::after { content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none;
background: linear-gradient(115deg, rgba(255,255,255,.12) 0%, rgba(255,255,255,.03) 24%, transparent 40%); }
/* status bar: the frame's top pixel row stretched, so it matches the game's own top colour */
.bar { position: absolute; top: 0; left: 0; right: 0; height: var(--barH); display: flex; align-items: center;
justify-content: space-between; padding: 0 8.5% 0 11.5%; font-family: -apple-system, 'SF Pro Text', sans-serif;
font-weight: 600; font-size: calc(var(--pw) * .041); z-index: 2; background: #0b0b10 center top / 100% 60000% no-repeat; }
.bar svg { height: calc(var(--pw) * .03); width: auto; margin-left: calc(var(--pw) * .012); }
.island { position: absolute; top: calc(var(--pw) * .025); left: 50%; transform: translateX(-50%); width: 30%; height: calc(var(--pw) * .082);
background: #000; border-radius: 999px; z-index: 3; }
.tablet .tel { border-radius: calc(var(--pw) * .065); padding: calc(var(--pw) * .02); }
.tablet .screen { border-radius: calc(var(--pw) * .048); }
.tablet .bar { padding: 0 5%; font-size: calc(var(--pw) * .024); }
.tablet .bar svg { height: calc(var(--pw) * .018); }
.tablet .island { width: calc(var(--pw) * .016); height: calc(var(--pw) * .016); top: calc(var(--pw) * .007); }
/* badges */
.badge { position: absolute; z-index: 9; display: flex; align-items: center; gap: calc(var(--u) * 2.2);
padding: calc(var(--u) * 1.7) calc(var(--u) * 3.6) calc(var(--u) * 1.7) calc(var(--u) * 1.7); border-radius: calc(var(--u) * 4);
background: linear-gradient(180deg, #ffffff 0%, #fff6e2 55%, #ffe3b0 100%);
box-shadow: inset 0 calc(var(--u) * -.7) 0 rgba(180,90,0,.18), 0 0 0 calc(var(--u) * .55) var(--ink),
0 calc(var(--u) * 1.1) 0 calc(var(--u) * .55) var(--ink), 0 calc(var(--u) * 3) calc(var(--u) * 5) rgba(0,0,0,.35);
transform: rotate(var(--br, 0deg)); color: var(--ink); white-space: nowrap; }
.badge .icon { flex: none; width: calc(var(--u) * 9); height: calc(var(--u) * 9); border-radius: calc(var(--u) * 2.6);
display: grid; place-items: center; background: var(--ic, linear-gradient(180deg, #ffd84d, #ff8a00));
box-shadow: inset 0 calc(var(--u) * .5) 0 rgba(255,255,255,.45), inset 0 calc(var(--u) * -.6) 0 rgba(0,0,0,.2); }
.badge .icon svg { width: 62%; height: 62%; overflow: visible; }
.badge b { display: block; font-family: var(--tf); font-weight: var(--tw); font-size: calc(var(--u) * 4.7); line-height: 1.02; }
.badge small { display: block; font-size: calc(var(--u) * 2.35); font-weight: 700; opacity: .72; margin-top: calc(var(--u) * .5); }
.badge.hot { background: linear-gradient(180deg, #fff27a 0%, #ffc21a 45%, #ff8a00 100%); padding: calc(var(--u) * 2) calc(var(--u) * 4.2); }
.badge.hot b { color: #fff; font-size: calc(var(--u) * 5.6); -webkit-text-stroke: calc(var(--u) * .5) #7a2c00; paint-order: stroke fill;
text-shadow: 0 calc(var(--u) * .5) 0 #7a2c00; }
.badge .stars { display: flex; gap: calc(var(--u) * .4); margin-right: calc(var(--u) * .6); }
.badge .stars svg { width: calc(var(--u) * 6.4); height: calc(var(--u) * 6.4); }
.badge .stars svg:nth-child(2) { transform: translateY(calc(var(--u) * -1)) scale(1.15); }
</style>
</head>
<body>
<div id="c"></div>
<script type="module">
const q = new URLSearchParams(location.search);
const W = +q.get('w'), H = +q.get('h'), LANG = q.get('lang'), SID = q.get('s');
const tablet = q.get('device') === 'tablet';
const job = await (await fetch(`/job.json?lang=${encodeURIComponent(LANG)}&s=${encodeURIComponent(SID)}`)).json();
const S = job.scene, C = job.copy, P = job.palette;
const u = W / 100, Hu = H / u;
const dev = tablet ? job.tablet : job.phone; // { w, h, bar } in CSS px of the raw frame
const FR = `/frames/${encodeURIComponent(LANG)}/${tablet ? 'tablet' : 'phone'}/`;
document.documentElement.lang = LANG;
const RTL = !!job.rtl;
const R = document.documentElement.style;
R.setProperty('--W', W + 'px'); R.setProperty('--H', H + 'px'); R.setProperty('--u', u + 'px');
R.setProperty('--ink', P.ink); R.setProperty('--glow', P.glow); R.setProperty('--shadow', P.shadow);
R.setProperty('--bg', `linear-gradient(180deg, ${P.bg[0]} 0%, ${P.bg[1]} 48%, ${P.bg[2]} 100%)`);
// A script the title font lacks (CJK, Arabic, Cyrillic, ...): the whole line switches to the Noto subset so a word
// never mixes two typefaces.
const T = job.fonts.title, B = job.fonts.body, X = job.fonts.script;
R.setProperty('--tf', X ? `'${X}', '${T}', sans-serif` : `'${T}', sans-serif`);
R.setProperty('--bf', X ? `'${X}', '${B}', sans-serif` : `'${B}', sans-serif`);
R.setProperty('--tw', X ? 900 : job.fonts.titleWeight ?? 400);
const CJK = /^(ja|ko|zh)/.test(LANG);
R.setProperty('--lh', /^(ar|hi|fa|ur|th)/.test(LANG) ? 1.28 : CJK ? 1.12 : .98);
if (X) { R.setProperty('--ls', '0'); R.setProperty('--kls', '0'); }
if (CJK) R.setProperty('--sk', '.06');
if (LANG.startsWith('ja')) document.documentElement.style.wordBreak = 'auto-phrase';
if (LANG.startsWith('ko')) document.documentElement.style.wordBreak = 'keep-all';
if (tablet) document.body.classList.add('tablet');
const tall = Hu > 200; // iOS 6.9" / 6.5" vs 16:9
R.setProperty('--top', tall ? 9 : 5.5);
R.setProperty('--fs', S.hero ? (tall ? 10.5 : 8.6) : (tall ? 12 : 9.8));
R.setProperty('--sub', tall ? 3.75 : 3.3);
const AR = dev.w / (dev.h + dev.bar);
R.setProperty('--ar', `${dev.w} / ${dev.h + dev.bar}`);
R.setProperty('--appH', `${100 * dev.h / (dev.h + dev.bar)}%`);
R.setProperty('--barH', `${100 * dev.bar / (dev.h + dev.bar)}%`);
const I = {
clock: '<svg viewBox="0 0 24 24"><circle cx="12" cy="13" r="8.5" fill="#fff" stroke="#5a2300" stroke-width="2"/><path d="M12 8v5l3.5 2.2" stroke="#5a2300" stroke-width="2.4" fill="none" stroke-linecap="round"/><rect x="9" y="1.5" width="6" height="3" rx="1.2" fill="#5a2300"/></svg>',
bolt: '<svg viewBox="0 0 24 24"><path d="M13.5 1.5 4 14h6.5L9.5 22.5 20 9.5h-6.8z" fill="#fff" stroke="#5a2300" stroke-width="1.8" stroke-linejoin="round"/></svg>',
burst: '<svg viewBox="0 0 24 24"><path d="M12 1l2.3 6 6.2-2-3.4 5.6 5.4 3.4-6.4.9.9 6.5L12 17l-5 4.4.9-6.5-6.4-.9 5.4-3.4L3.5 5l6.2 2z" fill="#fff" stroke="#5a2300" stroke-width="1.6" stroke-linejoin="round"/></svg>',
cube: '<svg viewBox="0 0 24 24"><path d="M12 2 21 7v10l-9 5-9-5V7z" fill="#fff" stroke="#0a4a2a" stroke-width="1.8" stroke-linejoin="round"/><path d="M3 7l9 5 9-5M12 12v10" stroke="#0a4a2a" stroke-width="1.8" fill="none"/></svg>',
map: '<svg viewBox="0 0 24 24"><path d="M3 6l6-2.5 6 2.5 6-2.5v14.5L15 20.5 9 18l-6 2.5z" fill="#fff" stroke="#06336e" stroke-width="1.8" stroke-linejoin="round"/><path d="M9 3.5V18M15 6v14.5" stroke="#06336e" stroke-width="1.8"/></svg>',
star: '<svg viewBox="0 0 24 24"><path d="M12 1.8l3 6.4 7 .9-5.1 4.8 1.3 6.9L12 17.4l-6.2 3.4 1.3-6.9L2 9.1l7-.9z" fill="url(#sg)" stroke="#6b2c00" stroke-width="1.6" stroke-linejoin="round"/><defs><linearGradient id="sg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fff59a"/><stop offset=".55" stop-color="#ffcc1a"/><stop offset="1" stop-color="#ff9000"/></linearGradient></defs></svg>',
grid: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="3" fill="#fff" stroke="#5e0636" stroke-width="1.8"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18" stroke="#5e0636" stroke-width="1.5"/></svg>',
gift: '<svg viewBox="0 0 24 24"><rect x="3" y="9" width="18" height="12" rx="2" fill="#fff" stroke="#5e0636" stroke-width="1.8"/><rect x="2" y="6" width="20" height="4" rx="1.5" fill="#fff" stroke="#5e0636" stroke-width="1.8"/><path d="M12 6v15M12 6C10 2 6 3 7.5 6M12 6c2-4 6-3 4.5 0" stroke="#5e0636" stroke-width="1.8" fill="none"/></svg>',
offline: '<svg viewBox="0 0 24 24"><path d="M2.5 9a14 14 0 0 1 19 0M6 12.5a9 9 0 0 1 12 0M9.4 16a4 4 0 0 1 5.2 0" stroke="#fff" stroke-width="2.4" fill="none" stroke-linecap="round"/><circle cx="12" cy="19.3" r="1.6" fill="#fff"/><path d="M3 3l18 18" stroke="#6e1300" stroke-width="4.4" stroke-linecap="round"/><path d="M3 3l18 18" stroke="#fff" stroke-width="2.2" stroke-linecap="round"/></svg>',
flag: '<svg viewBox="0 0 24 24"><path d="M5 22V3" stroke="#6e1300" stroke-width="2.4" stroke-linecap="round"/><path d="M5 3.5h13l-3 4 3 4H5z" fill="#fff" stroke="#6e1300" stroke-width="1.8" stroke-linejoin="round"/></svg>',
trophy: '<svg viewBox="0 0 24 24"><path d="M7 3h10v5a5 5 0 0 1-10 0z" fill="#fff" stroke="#6b2c00" stroke-width="1.8" stroke-linejoin="round"/><path d="M7 5H3.5a3.5 3.5 0 0 0 4 4M17 5h3.5a3.5 3.5 0 0 1-4 4M12 13v4M8 21h8l-1-4H9z" stroke="#6b2c00" stroke-width="1.8" fill="#fff" stroke-linejoin="round"/></svg>',
heart: '<svg viewBox="0 0 24 24"><path d="M12 21s-8.5-5.3-8.5-11.2A4.8 4.8 0 0 1 12 6.6a4.8 4.8 0 0 1 8.5 3.2C20.5 15.7 12 21 12 21z" fill="#fff" stroke="#5e0636" stroke-width="1.8" stroke-linejoin="round"/></svg>',
};
const ICON_BG = {
gold: 'linear-gradient(180deg,#ffe05a,#ff8a00)', green: 'linear-gradient(180deg,#8ff06a,#16a34a)',
blue: 'linear-gradient(180deg,#7fe0ff,#1f7aff)', violet: 'linear-gradient(180deg,#c49bff,#6a2fe0)',
pink: 'linear-gradient(180deg,#ffa3c8,#e8306f)', red: 'linear-gradient(180deg,#ff8a6a,#e4251b)',
};
// seeded rng: every render of a scene is identical
let seed = 0; for (const ch of SID + JSON.stringify(P.bg)) seed = (seed * 31 + ch.charCodeAt(0)) >>> 0;
const rnd = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296);
const c = document.getElementById('c');
// RTL: keep number runs ("72 / 80") left-to-right inside right-to-left text
const esc = s => { const t = String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;');
return RTL ? t.replace(/\d+(?:\s*\/\s*\d+)?/g, m => '⁦' + m + '⁩') : t; };
let top = '';
if (S.hero && job.logo) top += `<img class="logo" src="/logo">`;
else if (!S.hero && C.eyebrow) top += `<div class="eyebrow"><b>${String(job.number).padStart(2, '0')}</b>${esc(C.eyebrow)}</div>`;
top += `<h1><span>${esc(C.title?.[0])}</span><span>${esc(C.title?.[1])}</span></h1>`;
if (!S.hero && C.sub) top += `<p class="sub">${esc(C.sub)}</p>`;
c.innerHTML = `<div class="glow"></div><div class="rays"></div><div class="vign"></div><div class="top"${RTL ? ' dir="rtl"' : ''}>${top}</div>`;
await document.fonts.ready;
await Promise.all([...document.images].map(i => i.decode().catch(() => {})));
for (const sp of c.querySelectorAll('h1 span')) { // shrink a line that would overflow (long words)
const max = W - 10 * u, wd = sp.getBoundingClientRect().width;
let f = 1; while (wd * f > max && f > .55) f -= .02;
if (f < 1) sp.style.fontSize = `calc(${getComputedStyle(sp).fontSize} * ${f.toFixed(2)})`;
}
const textBottom = c.querySelector('.top').getBoundingClientRect().bottom / u;
const frames = S.frames.map(f => ({ x: 0, dy: 0, scale: 1, rotate: 0, ...f }));
const front = frames[0];
const pTop = textBottom + (S.hero ? 4 : 5.5);
const room = Hu - pTop + (S.over ?? 7);
const pw = Math.min(tablet ? 82 : 78, Math.max(56, room * AR)) * front.scale;
const ph = pw / AR;
R.setProperty('--rayY', (pTop + ph * .3) * u + 'px');
R.setProperty('--rayRot', (rnd() * 20) + 'deg');
const BAR = `<span>9:41</span><span style="display:flex;align-items:center">
<svg viewBox="0 0 18 12"><g fill="#fff"><rect x="0" y="8" width="3" height="4" rx=".8"/><rect x="5" y="5.5" width="3" height="6.5" rx=".8"/><rect x="10" y="3" width="3" height="9" rx=".8"/><rect x="15" y="0" width="3" height="12" rx=".8"/></g></svg>
<svg viewBox="0 0 16 12"><path fill="#fff" d="M8 2.2c2.3 0 4.4.9 6 2.4l1.3-1.3A10.3 10.3 0 0 0 8 .4C5.2.4 2.6 1.5.7 3.3L2 4.6a8.5 8.5 0 0 1 6-2.4zm0 3.6c1.3 0 2.5.5 3.4 1.3l1.3-1.3A6.6 6.6 0 0 0 8 4c-1.8 0-3.4.7-4.7 1.8l1.3 1.3C5.5 6.3 6.7 5.8 8 5.8zm0 3.6c.4 0 .8.2 1.1.4L8 11.6 6.9 9.8c.3-.2.7-.4 1.1-.4z"/></svg>
<svg viewBox="0 0 27 13"><rect x=".5" y=".5" width="23" height="12" rx="3.5" fill="none" stroke="rgba(255,255,255,.45)"/><rect x="2" y="2" width="20" height="9" rx="2.2" fill="#fff"/><path d="M25 4.5v4c.8-.3 1.4-1.1 1.4-2s-.6-1.7-1.4-2z" fill="rgba(255,255,255,.45)"/></svg></span>`;
let html = '';
for (let i = 0; i < 26; i++) {
const x = rnd() * 100, y = textBottom + rnd() * (Hu - textBottom);
if (i % 2) html += `<i class="spark" style="left:${x * u}px;top:${y * u}px;--s:${(1.6 + rnd() * 3.2) * u}px;--r:${rnd() * 45}deg;--o:${.55 + rnd() * .45}"></i>`;
else html += `<i class="dot" style="left:${x * u}px;top:${y * u}px;--s:${(3 + rnd() * 9) * u}px"></i>`;
}
for (let i = 0; i < 8; i++) {
const x = i % 2 ? 1 + rnd() * 6 : 93 + rnd() * 6, y = 2 + rnd() * (textBottom - 4);
html += `<i class="spark" style="left:${x * u}px;top:${y * u}px;--s:${(1.2 + rnd() * 2) * u}px;--r:0deg;--o:${.4 + rnd() * .4}"></i>`;
}
const phone = (f, cls) => {
const w = pw / front.scale * f.scale, src = FR + encodeURIComponent(f.src);
return `<div class="tel ${cls}" style="--pw:${w * u}px;--px:${(50 + f.x) * u}px;--py:${(pTop + f.dy) * u}px;--pr:${f.rotate}deg"><div class="screen">
<div class="bar" style="background-image:url('${src}')">${BAR}</div><div class="island"></div><img class="app" src="${src}"></div></div>`;
};
for (const f of frames.slice(1)) html += phone(f, 'back');
html += phone(front, 'front');
(S.badges ?? []).forEach((b, i) => {
const [big, small] = (C.badges ?? [])[i] ?? [''];
if (!big) return;
const ic = b.stars ? `<span class="stars">${I.star}${I.star}${I.star}</span>`
: b.hot ? '' : `<span class="icon" style="--ic:${ICON_BG[b.iconBg] ?? b.iconBg ?? ICON_BG.gold}">${I[b.icon] ?? I.star}</span>`;
const pos = b.side === 'l' ? `left:${4 * u}px` : `right:${4 * u}px`;
html += `<div class="badge${b.hot ? ' hot' : ''}"${RTL ? ' dir="rtl"' : ''} style="${pos};top:${Math.min(pTop + ph * b.y, Hu - 18) * u}px;--br:${b.rot || 0}deg">${ic}
<div${RTL ? ' dir="rtl"' : ''}><b>${esc(big)}</b>${small ? `<small>${esc(small)}</small>` : ''}</div></div>`;
});
c.insertAdjacentHTML('beforeend', html);
await Promise.all([...document.images].map(i => i.decode().catch(() => {})));
document.body.dataset.ready = '1';
</script>
</body>
</html>

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "crt-ship",
"version": "0.1.0",
"description": "My toolchain for shipping mobile games to Google Play and the App Store: a Bun CLI plus Claude Code skills.",
"license": "MIT",
"author": "CRTkafa",
"type": "module",
"repository": { "type": "git", "url": "https://git.crt.fyi/CRT/crt-ship.git" },
"keywords": ["google-play", "app-store-connect", "mobile-games", "store-listing", "in-app-purchases", "screenshots", "claude-code-plugin"],
"bin": { "crt-ship": "./cli/index.ts" },
"exports": { ".": "./cli/lib/config.ts" },
"files": ["cli", "templates", "skills", "bin", ".claude-plugin", "README.md", "LICENSE", "CHANGELOG.md"],
"engines": { "bun": ">=1.1" },
"scripts": {
"test": "bun test",
"typecheck": "tsc --noEmit",
"check": "bun run typecheck && bun test"
},
"devDependencies": {
"@types/bun": "1.3.0",
"typescript": "5.9.3"
}
}

View file

@ -0,0 +1,39 @@
---
name: ads-levelplay-checks
description: Diagnose and verify ad mediation (Unity LevelPlay / ironSource and its networks like Unity Ads, InMobi, Liftoff, AdMob) in a mobile game before and after store release - app keys, game ids, adapter versions, integration validation, "Invalid Game ID", "Mediation No fill" 509, app-ads.txt, SKAdNetwork ids, ATT, COPPA setting. Use when ads do not load, a network reports invalid/unknown ids, or the user prepares ads for launch.
---
# Ad mediation checks (LevelPlay)
No CLI needed; these are checks to run and read. Ask the owner before changing dashboard settings.
## Checklist
1. **Ids per platform.** LevelPlay app key + ad unit ids for iOS and Android are different; ship production ids only
in release builds and use the public demo app keys from Unity's Mediation demo apps during development so real units
never see test traffic.
2. **Integration.** After init call the SDK's `validateIntegration()`; the device log (Xcode console, Android logcat
tag `IntegrationHelper`) must list every network as "Adapter - Version x - VERIFIED". Adapter + network SDK versions
must be a pair from Unity's adapter tables / CHANGELOGs.
3. **Unity Ads "Invalid Game ID" / iOS "Initialization failed: not found".** Often account-side, not code: compare
`https://publisher-config.unityads.unity3d.com/games/<gameId>/configuration` for your id with Unity's demo id
`14851` (which answers with a config). If yours 404s or is empty, the Unity Ads project / game id is not set up or not
linked to the LevelPlay app: fix it in the Unity dashboard (Monetization > Projects > the game ids, and the LevelPlay
network setup using exactly those ids).
4. **509 "Mediation No fill"** before launch is normal: the LevelPlay app is not live / not linked to store URLs. After
both listings are public, set the store URLs in the LevelPlay dashboard.
5. **app-ads.txt** on the root of the developer website named in both listings (`https://<domain>/app-ads.txt`):
`OWNERDOMAIN=<domain>`, the LevelPlay DIRECT line with your publisher id, and each network's lines (Unity's
"App-Ads.txt" doc page lists them). Re-sync now and then. The listing website must match the domain exactly.
6. **iOS**: SKAdNetwork ids of every network in Info.plist (refresh from each network's official list when adding one),
`NSUserTrackingUsageDescription`, ATT prompt before the ad SDK starts, `-ObjC` linker flag if the SDK requires it.
7. **Android**: `AD_ID` permission (declared in Play's advertising ID form), network SDKs listed explicitly when the
adapter artifacts declare no dependencies.
8. **Privacy**: COPPA "not directed at children" unless the app is for kids; EU / UK / CH consent (CMP) or
non-personalized ads; Data safety + App Privacy list the ad data as shared / tracking (`store-declarations`).
9. **Placement sanity**: no ads in the first sessions / levels, never mid-round, rewarded always optional; reviewers
test the first minutes.
## Gotchas
- Adding Google AdMob needs its app id first: the adapter crashes at launch without the manifest / Info.plist id.
- Test ads on real units can get the account flagged: use demo ids or registered test devices.
- A network failing to init does not break mediation; the others still fill once the app is live.

34
skills/app-icons/SKILL.md Normal file
View file

@ -0,0 +1,34 @@
---
name: app-icons
description: Generate every app icon a mobile game needs from one artwork - App Store 1024 (no alpha), Play 512, Android adaptive foreground/background, legacy round and square launcher icons, iOS sizes via @capacitor/assets, web favicons, apple-touch and maskable icons. Use when the user wants app icons, launcher icons, adaptive icons, favicons, or an icon looks cropped/cut off on Android or is rejected for alpha on iOS.
---
# App icons from one artwork
CLI: `crt-ship icons` (or `bun ${CLAUDE_PLUGIN_ROOT}/cli/index.ts icons`). Needs `ffmpeg` (`cwebp` optional).
## Inputs (`icons` in crt-ship.config.ts)
- `composite`: 1024 x 1024 opaque full icon (stores, iOS, web)
- `foreground`: 1024 RGBA, the subject on transparency (adaptive + maskable)
- `background`: 1024 opaque background layer
- outputs: `out` (store icons), `web`, `capacitorAssets` + `capacitorCommand`, `androidRes`
## Steps
1. Put the three PNGs in the project, set the paths, `crt-ship check`.
2. `crt-ship icons --dry` lists every file it will write; then `crt-ship icons`.
3. With Capacitor: set `capacitorAssets` (e.g. `assets`) and
`capacitorCommand: 'bunx @capacitor/assets generate --assetPath assets'`; crt-ship writes the three sources, runs it,
and then redraws the Android legacy icons (step order matters).
4. Verify: `sips -g hasAlpha store/icons/app-store-1024.png` says no; install on an Android device with a circle
launcher mask and an old launcher (legacy icons); check the web favicon and the PWA maskable preview.
## Gotchas
- **Adaptive foreground scale ~0.7**: the anydpi-v26 xml insets both layers 16.7 %, and launchers mask the visible
part (often a circle). At 0.7 the subject's corners sit just inside the circle. Tune with `adaptiveScale`.
- **Legacy icons**: @capacitor/assets crops the full square artwork into `ic_launcher` / `ic_launcher_round`, cutting
off anything near the corners. crt-ship redraws them from the adaptive composite with a circle and a 22 % rounded
square mask (4x supersampled).
- **App Store 1024 must have no alpha channel** (upload or review fails); crt-ship writes rgb24.
- Stores and launchers round the corners themselves: deliver full squares, never pre-rounded icons.
- Web icons as 256-colour PNGs (about a third of the size); maskable icon keeps the subject in the 80 % safe zone.
- Play listing icon is 512 x 512, separate from the launcher icons in the AAB.

View file

@ -0,0 +1,67 @@
---
name: appstore-first-release
description: Step-by-step first (and later) App Store release of a mobile game - App Store Connect API key, bundle id, app record, signing and xcodebuild archive/upload with the API key, TestFlight, version + listing text in many locales, screenshots, age rating, App Privacy, review details, in-app purchases with the first version, and submitting for review. Use when the user wants to publish, release, upload or set up an iOS game/app on the App Store or TestFlight, or hits App Store Connect API errors.
---
# App Store: first release
The CLI is `crt-ship` (on PATH while this plugin is enabled; otherwise `bun ${CLAUDE_PLUGIN_ROOT}/cli/index.ts`).
Config: `crt-ship.config.ts`; credentials: `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_P8` (env or
`.crt-ship/credentials.json`, gitignored). All commands are idempotent and have `--dry`. Run `crt-ship check` first.
**Ask the owner before submitting for review, releasing, or canceling a submission.** The final "Submit for Review"
of a first version is the owner's click.
## Steps (API = crt-ship, UI = App Store Connect / Xcode by hand)
1. **UI · API key.** App Store Connect > Users and Access > Integrations > App Store Connect API > Team Keys > +
(Admin or App Manager). Download `AuthKey_<KEYID>.p8` once, keep it in `~/.appstoreconnect/private_keys/`, note the
Key ID and Issuer ID. The same key signs builds (`-allowProvisioningUpdates`).
2. **API · Bundle id.** `crt-ship apple bundle` registers it (Certificates, Identifiers & Profiles).
3. **UI · App record.** Apps > + > New App: iOS, name, primary language, the bundle id, SKU. **No API for this.**
Then `crt-ship apple status` finds it (put the Apple ID in `apple.appId` to skip the lookup).
4. **Local · Info.plist.** `ITSAppUsesNonExemptEncryption = NO` (HTTPS only) so builds skip export compliance;
`NSUserTrackingUsageDescription` if ads use the IDFA; privacy policy reachable inside the app (guideline 5.1.1).
5. **Local · Build + upload** (Mac, no Xcode account login needed, the API key does signing):
```sh
K="-allowProvisioningUpdates -authenticationKeyPath $ASC_P8 -authenticationKeyID $ASC_KEY_ID -authenticationKeyIssuerID $ASC_ISSUER_ID"
xcodebuild -project ios/App/App.xcodeproj -scheme App -configuration Release -destination generic/platform=iOS \
-archivePath build/App.xcarchive MARKETING_VERSION=1.0.0 CURRENT_PROJECT_VERSION=3 $K archive
xcodebuild -exportArchive -archivePath build/App.xcarchive -exportOptionsPlist ExportOptions.plist -exportPath build/export $K
```
ExportOptions.plist: `method` app-store-connect, `destination` upload, `teamID` <your team>, `signingStyle`
automatic. `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION` overrides beat the project values; the build number must
grow per version. Processing takes 5-30 min (`crt-ship apple status` shows VALID).
6. **UI · TestFlight.** Create an internal group with "access to all builds" (hasAccessToAllBuilds) once: every new
build reaches the testers automatically. Sandbox testers: Users and Access > Sandbox.
7. **API · Version + text.** Fill `store/appstore.md`, then `crt-ship apple listing --version 1.0.0 --build 3`
(version, copyright, MANUAL release, build, name / subtitle / privacy URL, description / keywords / promo / URLs).
8. **API · Screenshots.** `store-screenshots` skill, then
`crt-ship apple screenshots > .crt-ship/asc-shots.log 2>&1 &` (6.9" + 6.5" per locale, MD5-idempotent).
9. **API · App info.** `crt-ship apple appinfo`: categories, age rating declaration, content rights, Free price,
all territories.
10. **API · Review details.** `crt-ship apple review-details` (contact from `.crt-ship/credentials.json`
`reviewContact`, or `--contact-from <Apple ID of another app of the team>`; notes from the config).
11. **API + UI · In-app purchases.** `store-iap-setup` skill. First IAPs go to review **with** this version.
12. **UI · App Privacy.** `store-declarations` skill (nutrition labels), then **Publish**.
13. **UI · Submit.** Version page > Add for Review (with the IAPs in the same draft submission) > Submit for Review
(owner). Release type MANUAL means the owner presses "Release" after approval.
14. **Verify.** `crt-ship apple status`: version WAITING_FOR_REVIEW, IAPs WAITING_FOR_REVIEW, one review submission.
## Gotchas
- The app record cannot be created via API; the bundle id can.
- First consumable / non-consumable IAPs must be submitted together with an app version, only from the IAP page
("Add for Review" puts them into the draft submission). The API (`inAppPurchaseSubmissions`) answers
`FIRST_*_MUST_BE_SUBMITTED_ON_VERSION`. If the version was already submitted without them: cancel the review
submission (`crt-ship apple status --cancel-submission <id>`, PATCH canceled: true), wait a minute, add the IAPs
from their page, then resubmit.
- App Privacy (data types, purposes, linked, tracking) is UI-only and must be **published**, not just saved.
- Screenshot sizes: 6.9" 1320 x 2868 (`APP_IPHONE_67`) is required; 6.5" 1284 x 2778 (`APP_IPHONE_65`) recommended.
PNG without alpha. Up to 10 per set. Apple processes uploads async; failures show as FAILED later.
- App Store icon (1024) must have **no alpha channel** (`crt-ship icons` writes rgb24).
- Limits: name 30, subtitle 30, promotional text 170, keywords 100 bytes (UTF-8, no spaces after commas),
description 4000. No "What's New" on a first version.
- Guideline 5.1.1: the privacy policy link must be inside the app too (Settings > Privacy), not only in the listing.
- `xcodebuild` with Capacitor needs a UTF-8 locale (`export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8`).
- 500 / 503 from the API happen; crt-ship retries with backoff.

View file

@ -0,0 +1,67 @@
---
name: play-first-release
description: Step-by-step first (and later) Google Play release of a mobile game - Play Console app creation, service account + API access, signing, AAB upload to internal testing, store listing, graphics, data safety, App content declarations, production draft and sending for review. Use when the user wants to publish, release, upload or set up an Android game/app on Google Play, push a Play listing or AAB, or hits Play Developer API errors (edits, "outside of this Edit", draft app releases).
---
# Google Play: first release
The CLI is `crt-ship` (on PATH while this plugin is enabled; otherwise `bun ${CLAUDE_PLUGIN_ROOT}/cli/index.ts`).
It reads `crt-ship.config.ts` in the project (create with `crt-ship init`) and credentials from env or
`.crt-ship/credentials.json` (gitignored). Every write command has `--dry`. Run `crt-ship check` first.
**Ask the owner before anything public or irreversible**: production releases, "Send for review", publishing,
deleting products. Sending a first release for review is the owner's action in the Console, not yours.
## Steps (API = crt-ship does it, UI = Play Console by hand)
1. **UI · Create the app.** Play Console > Home > Create app: name, default language, Game, Free, declarations.
The package name is fixed by the first uploaded AAB.
2. **UI · API access.** Google Cloud project (any) > enable "Google Play Android Developer API" > create a service
account > JSON key (keep it outside the repo). Play Console > Users and permissions > Invite new users > the
service account email > app permissions: view app information, manage store presence, release to testing tracks,
manage testing tracks, release to production (only if you want API production drafts), manage orders (for purchase
checks). Put the key path in `PLAY_SERVICE_ACCOUNT`. Permissions can take a few minutes to apply.
3. **Local · Signing.** Generate an upload key (`keytool -genkeypair -v -storetype PKCS12 -keystore upload.p12
-alias upload -keyalg RSA -keysize 4096 -validity 10950`), keep keystore + passwords out of git and back them up
(encrypted, two places). Play App Signing holds the real app key; a lost upload key can be reset via the Console.
Check the AAB signer before uploading: `keytool -printcert -jarfile app-release.aab`.
4. **Local · Version.** versionName (e.g. 1.0.0) and a versionCode that always increases. Capacitor: set them in
`android/app/build.gradle` (or env-driven) before `./gradlew bundleRelease`.
5. **UI or API · First upload.** The very first AAB may need the Console (Testing > Internal testing > Create new
release) so Play links the package name; after that: `crt-ship play release app-release.aab` (internal, completed).
Add testers (internal testing list) and **license testers** (Settings > License testing) so test purchases are free.
6. **API · Listing text.** Fill `store/listing.md` (one section per language), then `crt-ship play listing`.
It also sets contact email + website (website = the domain serving app-ads.txt).
7. **API · Graphics.** Render (`store-screenshots` skill), `crt-ship shots jpeg`, then
`crt-ship play images > .crt-ship/play-images.log 2>&1 &` and follow the log (it is slow: one edit per language).
8. **API · In-app products.** `store-iap-setup` skill (`crt-ship play products`). Needs a build with the BILLING
permission on any track first.
9. **API · Data safety.** Answers in `dataSafety` of the config, `crt-ship play data-safety`, then check the summary
in Policy and programs > App content > Data safety.
10. **UI · App content.** `store-declarations` skill: privacy policy, ads, app access, content rating (IARC), target
audience, advertising ID, government / financial / health / news, data safety review.
11. **UI · Store settings.** Category + tags, contact details.
12. **API + UI · Production.** `crt-ship play release --version-code N --production draft`, then in the Console:
Production > Countries/regions > Add countries > select all > Save; Production > the draft release > Edit >
Next > Save; Publishing overview > **Send changes for review** (owner).
13. **Verify.** `crt-ship play status` (tracks, listings, products), Console dashboard has no open tasks,
Publishing overview shows "In review".
## Gotchas
- **Draft app**: until the first release is reviewed, the API only accepts `status: draft` ("Only releases with
status draft may be created on draft app"). Create the production draft via API, finish in the Console (step 12).
- **"A change was made to the application outside of this Edit"**: any Console save or other edit commit invalidates
an open API edit. `crt-ship` redoes the edit automatically (3 retries) and uses one edit per language for images, but
do not save things in the Console while uploads run.
- Uploads (AAB, 100+ images) take minutes: run them in the background with a log file and watch progress lines.
- **Personal developer accounts** created after Nov 2023 need a closed test with 12+ testers for 14 days before
production; **organization accounts** skip that rule.
- Phone screenshots: 2-8, sides 320-3840 px, long side at most 2x the short side (1080 x 1920 fine, 1080 x 2400 not).
Feature graphic 1024 x 500 required. PNG without alpha, or JPEG (q92 is 4x smaller and uploads faster).
- Play listing limits: title 30, short 80, full 4000, release notes 500 (checked locally before upload).
- The legacy `inappproducts` API returns 403 "Please migrate to the new publishing API": use
`monetization.onetimeproducts` (what `crt-ship play products` does).
- Wrong path casing on the new product endpoints returns an HTML 404, not JSON (`oneTimeProducts` camelCase for list
and batchUpdateStates).
- Website in the listing must be the exact domain whose `/app-ads.txt` lists your ad networks.

View file

@ -0,0 +1,31 @@
---
name: privacy-policy
description: Write and host one studio-wide privacy policy page for all of a developer's mobile games, with a per-game services table (analytics, ads, server, purchases), children, retention and contact sections, linked from both store listings and inside each game. Use when the user needs a privacy policy, privacy URL for Google Play / App Store, or must update the policy after adding an SDK.
---
# Studio-wide privacy policy
Template: `store/privacy-policy.html` (from `crt-ship init`, or `${CLAUDE_PLUGIN_ROOT}/templates/privacy-policy.html`).
A single static page, light/dark, mobile friendly, English + a second language section.
## Steps
1. **One page for the studio, one row per game and service.** Columns: game, service (analytics, ad mediation +
networks, your server, app stores), what data and why, the player's choice (in-game switch, ATT prompt, OS ad
settings, "remove ads"). Remove what you do not use; never list services you do not ship.
2. Sections to keep: what we use (table), children (general audience, not directed at under 13 unless you really are
a kids app), retention and rights (how to request a copy / deletion, response time), security (HTTPS), changes
(date at the top), contact email.
3. **Host** it at a stable URL on your developer website, the same domain that serves `app-ads.txt` and is named as
website in both listings (e.g. `https://example.com/privacy`). Any static host works.
4. **Link it everywhere**: `app.privacyPolicyUrl` in `crt-ship.config.ts` (pushed by `crt-ship apple listing`), Play
Console > App content > Privacy policy, and **inside the game** (Settings > Privacy opens the URL): App Store
guideline 5.1.1 requires the in-app link.
5. **Verify**: the URL answers 200 over HTTPS without login or cookie wall, the table matches the Data safety /
App Privacy answers (`store-declarations` skill), the date is current.
## Gotchas
- Update the policy first, then the store forms, whenever a game adds an SDK or starts storing something new.
- The policy must not be a PDF or a Google Doc behind a login; a plain page is safest for review.
- Do not put a personal address or private email in it; use a studio contact address.
- If a game uses ads in the EU / EEA / UK / CH, say how consent works (CMP or non-personalized ads).
- This template is a starting point, not legal advice.

View file

@ -0,0 +1,43 @@
---
name: store-declarations
description: Answer the store compliance forms for a mobile game - Google Play Data safety (API CSV), App content declarations (ads, app access, content rating IARC, target audience, advertising ID, government/financial/health/news), App Store App Privacy nutrition labels, age rating, export compliance. Use when the user fills in data safety, privacy labels, content rating, target audience, ads or advertising ID declarations, or gets Data safety CSV import errors.
---
# Store declarations
Templates: `store/declarations.md` (Play App content + IARC answers), `store/app-privacy.md` (Apple labels), both
from `crt-ship init`. Data safety answers live in `dataSafety` of `crt-ship.config.ts`.
CLI: `crt-ship` (or `bun ${CLAUDE_PLUGIN_ROOT}/cli/index.ts`).
## Order
1. **Inventory first.** List every SDK and server call: what leaves the device, who receives it, can the player turn
it off. The privacy policy (`privacy-policy` skill) must say the same; update it before the forms.
2. **Play Data safety (API).** Fill `dataSafety` (types not listed = not collected; `shared` = third party that is not
your service provider, e.g. ad networks; analytics vendors acting as processors are "collected" only).
`crt-ship play data-safety --dry --csv store/data-safety.csv` to review, then `crt-ship play data-safety`.
Then check the summary in Play Console > Policy and programs > App content > Data safety.
3. **Play App content (UI).** Policy and programs > App content, each card with `store/declarations.md`:
privacy policy, app access, ads, content rating (IARC questionnaire), target audience and content, news, COVID-19,
data safety, government apps, financial features, health, advertising ID.
4. **Apple age rating (API).** `crt-ship apple appinfo` (all NONE except your overrides, e.g. `advertising: true`).
5. **Apple App Privacy (UI only).** App Store Connect > the app > App Privacy > Get Started / Edit, from
`store/app-privacy.md`; per data type: purposes > linked to identity > used for tracking. Then **Publish**.
6. **Export compliance.** `ITSAppUsesNonExemptEncryption = NO` in Info.plist (standard HTTPS only).
## Typical F2P game with ads + IAP (no accounts)
Data safety: approximate location (IP country; shared, analytics + ads + fraud), app interactions (shared, analytics +
ads), other app activity (save backup, functionality), diagnostics (shared), device or other IDs (shared, ad id),
purchase history (if your server checks receipts). Encrypted in transit: yes. Deletion: yes, by email. Account
creation: none. IARC: everything No except digital purchases Yes; target audience 13+; ads Yes; advertising ID yes
(advertising, analytics, fraud prevention); sign-in details: no login needed.
## Gotchas
- The Data safety CSV import **replaces** the whole form.
- `PSL_SUPPORTED_ACCOUNT_CREATION_METHODS` (e.g. `PSL_ACM_NONE`) is required even though Google's older sample CSV
omits it: without it the import fails with "Response missing". crt-ship always writes it.
- "Optional" data is only allowed when the user can really choose for that data; otherwise required.
- Ads SDKs: read each network's own Play data safety guidance; adding a network can add data types.
- Target audience under 13 pulls the app into the Families policy (certified ad networks, no ad id): only if true.
- Apple: "linked to the user" is No when there are no accounts and nothing ties data to a name / email / phone.
- App Privacy saved but not published = not submitted.
- Keep store answers, the privacy policy and the in-app privacy settings consistent; reviewers compare them.

View file

@ -0,0 +1,52 @@
---
name: store-iap-setup
description: Create and price in-app purchases (consumables, non-consumables like "remove ads", coin/gem packs, starter packs) on Google Play and App Store Connect from one product list - regional prices, local overrides, activation, review screenshots, submitting first IAPs with a version, license/sandbox testers. Use when the user sets up, prices, updates or tests in-app products/IAPs, or hits Play onetimeproducts / inappproducts 403 or App Store IAP submission errors.
---
# In-app purchases on both stores
CLI: `crt-ship` (or `bun ${CLAUDE_PLUGIN_ROOT}/cli/index.ts`). One list in `crt-ship.config.ts` `products[]` drives
both stores: `id` (same on both), `type` (consumable | non_consumable), `usd` base price, optional `overrides`
(`{ region: 'TR', currency: 'TRY', price: 99.99 }`), `listings` per language (title <= 30, description <= 55 for
Apple), `reviewScreenshot`.
## Prerequisites
- Play: a build with `com.android.vending.BILLING` uploaded to any track; payments profile set up.
- Apple: Paid Applications agreement + banking + tax active (Business > Agreements), the app record exists.
## Google Play (API)
1. `crt-ship check`, then `crt-ship play products --dry` (shows create/update and region count per product).
2. `crt-ship play products`. Per product: `pricing:convertRegionPrices` (USD -> every region at local price points,
and the `regionsVersion`), your overrides on top, `PATCH .../onetimeproducts/{id}?allowMissing=true&regionsVersion.version=...`
(create or update), then `oneTimeProducts/{id}/purchaseOptions:batchUpdateStates` to activate (new options are DRAFT).
3. Verify: `crt-ship play status` lists every product with `buy:ACTIVE`.
4. Test: Play Console > Settings > License testing: add tester Gmail accounts; install from the internal testing
link; purchases are free test orders.
## App Store (API + UI)
1. `crt-ship apple iap --dry`, then `crt-ship apple iap`: creates missing products, localizations, a price schedule
(USA base + overrides; Apple equalizes the rest) and availability. Existing schedules are not changed (change prices
in the UI if needed).
2. Review screenshot per product (a real device size, e.g. 1290 x 2796, showing the item in the shop):
`crt-ship apple iap-review`. States move to READY_TO_SUBMIT when complete.
3. **UI**: each IAP page > "Add for Review"; this puts them into the version's draft submission. Then submit the
version (owner, see appstore-first-release).
4. Test: Sandbox testers (Users and Access > Sandbox) on device; or a StoreKit configuration file in Xcode for local
runs (those transactions are signed locally and fail server-side receipt checks).
## Gotchas
- Play legacy `inappproducts` returns 403 "Please migrate to the new publishing API" -> use
`monetization.onetimeproducts`.
- Path casing: PATCH works on `onetimeproducts`, but list + `purchaseOptions:batchUpdateStates` need `oneTimeProducts`;
the wrong casing answers with an HTML 404 page.
- Play has no consumable flag: the app consumes the purchase after granting it. Use `legacyCompatible: true` so older
billing libraries see the product by id.
- Apple: first IAPs must ship with an app version ("FIRST_*_MUST_BE_SUBMITTED_ON_VERSION" from
inAppPurchaseSubmissions). If the version is already in review without them: cancel the review submission
(`crt-ship apple status --cancel-submission <id>`, ask the owner), re-add via "Add for Review", resubmit.
- Apple needs an exact existing price point for each manual price; the error lists the nearest ones.
- Apple IAP description max 55 characters, display name max 30.
- Product ids can never be reused after deletion on either store. Never delete without the owner.
- Do not put prices or product names in store descriptions; say "offers optional in-app purchases".
- If the game ships without real billing yet, hide every purchase surface in store builds (review rejects mock
purchases) and answer "digital purchases: No" in IARC.

View file

@ -0,0 +1,45 @@
---
name: store-screenshots
description: Produce store screenshots and the Play feature graphic for a mobile game in every required size and many languages - capture raw frames from the running game with playwright, write per-language marketing copy (incl. RTL Arabic and CJK fonts), render captioned scenes with device frames, JPEG copies for Play, and upload to both stores. Use when the user needs App Store / Google Play screenshots, preview images, a feature graphic, localized screenshot captions, or screenshot sizes.
---
# Store screenshots
CLI: `crt-ship` (or `bun ${CLAUDE_PLUGIN_ROOT}/cli/index.ts`). Needs `ffmpeg`, and `playwright` in the project
(`bun add -d playwright`; it uses Playwright's Chromium or an installed Google Chrome).
## Sizes (what each store wants)
| size key | pixels | where |
|---|---|---|
| ios69 | 1320 x 2868 | App Store 6.9" (`APP_IPHONE_67`), required |
| ios65 | 1284 x 2778 | App Store 6.5" (`APP_IPHONE_65`) |
| play | 1080 x 1920 | Play phone (2-8, ratio at most 2:1) |
| tab7 | 1080 x 1920 | Play 7" tablet (tablet frames) |
| tab10 | 1440 x 2560 | Play 10" tablet |
| feature | 1024 x 500 | Play feature graphic (required) |
## Steps
1. **Game switches.** Give the game URL params for screenshots: language, fixed seed, no ads / analytics / purchase
UI, skip tutorial, jump to a level, freeze at a moment. Frames must show real gameplay (store rule).
2. **Capture.** Configure `shots.capture` (baseUrl, query with `{lang}`, shots: path / waitFor / eval / delayMs /
device). Start the game (production build preview), then `crt-ship shots capture --lang en` (phone 440 x 902 @3x
and tablet 800 x 1240 @2x by default; the template draws the status bar above). Frames are local, not committed.
3. **Copy.** `store/shots/copy.json`: per language, per scene: `eyebrow`, `title` [white line, gold line], `sub`,
`badges` [[big, small]]. Short and true (only claim what is in the game); about 15 Latin characters per title line.
Natural marketing tone per language, not literal translation; use the game's own terms.
4. **Fonts.** `crt-ship shots fonts` downloads title/body fonts and a Noto subset for every non-Latin script found in
the copy (CJK, Arabic, Hebrew, Devanagari, Thai, Cyrillic/Greek). Re-run after copy edits.
5. **Scenes.** `shots.scenes`: palette (fire, violet, blast, green, night, sky, gold, candy or custom), frames (one
front phone + optional back phones with x / dy / scale / rotate), badge slots (y, side, icon, hot, stars).
6. **Render.** `crt-ship shots render --lang en` first, review `store/renders/en/sheet.png` (contact sheet), then
all languages in the background: `crt-ship shots render > .crt-ship/render.log 2>&1 &`.
7. **JPEG for Play.** `crt-ship shots jpeg` (q92, about 4x smaller than PNG; App Store stays PNG).
8. **Upload.** `crt-ship play images` and `crt-ship apple screenshots` (both idempotent, background + log).
## Gotchas
- Output is 24-bit RGB PNG: Play rejects alpha / palette PNGs, Apple rejects alpha.
- Arabic: `rtl: true` on the language; numbers stay LTR inside RTL text automatically.
- Japanese breaks by phrase, Korean at spaces; long words shrink the line rather than wrap.
- Check every language's sheet for overflow before uploading; German / Russian titles run long.
- Play: 8 per type max; Apple: 10 per set max.
- Do not include prices, "free" badges for paid items, or other platforms' UI.

33
templates/app-privacy.md Normal file
View file

@ -0,0 +1,33 @@
# Example Game · App Store "App Privacy" answers
App Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press
**Publish** at the top of the App Privacy page (saving alone does not publish it, and review checks the published one).
Keep it consistent with the privacy policy and the Play Data safety answers (`dataSafety` in `crt-ship.config.ts`).
Typical free-to-play game: analytics SDK + ad mediation (with ATT) + optional server backup, no accounts.
**Do you or your third-party partners collect data from this app?** Yes
**Tracking**: Yes, if any ad network uses the IDFA after the App Tracking Transparency prompt (third-party advertising).
Then `NSUserTrackingUsageDescription` must be in Info.plist and the prompt must come before the ad SDK starts.
| data type | collected | used to track | linked to the user | purposes |
|---|---|---|---|---|
| Identifiers > Device ID (IDFA, analytics random id) | yes | yes (IDFA, with ATT consent) | no | Third-Party Advertising, Analytics, App Functionality |
| Location > Coarse Location (country from IP) | yes | yes | no | Third-Party Advertising, Analytics |
| Usage Data > Advertising Data (ads shown, ad taps) | yes | yes | no | Third-Party Advertising, Analytics |
| Usage Data > Product Interaction (sessions, level events) | yes | no | no | Analytics |
| User Content > Gameplay Content (save backup, scores) | only with a server backup | no | no | App Functionality |
| Diagnostics > Other Diagnostic Data (device model, OS version) | yes | no | no | Analytics, Third-Party Advertising |
| Purchases > Purchase History | only if your server stores receipts | no | yes if tied to an account | App Functionality |
"Linked to you" is **No** when there are no accounts and nothing is tied to a name, email or phone number.
Not collected (typical): Contact Info, Health and Fitness, Financial Info, Precise Location, Sensitive Info, Contacts,
Emails or Text Messages, Photos or Videos, Audio, Browsing History, Search History, Crash Data (unless you ship a crash
reporter).
Per data type the UI asks, in order: purposes (checkboxes) > "Is this data linked to the user's identity?" >
"Do you or your third-party partners use this data for tracking purposes?".
Privacy policy URL: App Information > Privacy Policy URL (set by `crt-ship apple listing`). Privacy choices URL:
optional, leave empty unless you have a page for it.

67
templates/appstore.md Normal file
View file

@ -0,0 +1,67 @@
# Example Game · App Store listing copy
Source for `crt-ship apple listing`. One `## <ASC locale>` section per locale (the first word is the locale code:
`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### <field>`, field names exactly:
`name` (optional, defaults to `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 bytes, comma
separated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed
on the first version). Shared values (privacy policy, support and marketing URL) come from `crt-ship.config.ts`.
---
## en-US · English (U.S.)
### subtitle
```
Short hook under the name
```
### promotionalText
```
One or two sentences you can change any time without a new version.
```
### keywords
```
puzzle,casual,relaxing,offline,brain,match,levels
```
### description
```
First paragraph: what the player does and why it feels good.
FEATURES
• One-thumb controls
• 100+ levels
• Play offline, no account needed
Free to play. Contains ads and offers optional in-app purchases.
```
## tr · Türkçe
### subtitle
```
İsmin altındaki kısa kanca
```
### promotionalText
```
Yeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.
```
### keywords
```
bulmaca,rahatlatıcı,internetsiz,zeka,eşleştirme,bölüm
```
### description
```
İlk paragraf: oyuncu ne yapıyor ve neden keyifli.
ÖZELLİKLER
• Tek parmakla kontrol
• 100+ bölüm
• İnternetsiz oyna, hesap gerekmez
Ücretsiz oyun. Reklam içerir ve isteğe bağlı uygulama içi satın alımlar sunar.
```

View file

@ -0,0 +1,7 @@
{
"PLAY_SERVICE_ACCOUNT": "/absolute/path/to/play-service-account.json",
"ASC_KEY_ID": "ABC123DEFG",
"ASC_ISSUER_ID": "00000000-0000-0000-0000-000000000000",
"ASC_P8": "/absolute/path/to/AuthKey_<KEY_ID>.p8",
"reviewContact": { "firstName": "", "lastName": "", "phone": "+1 555 0100", "email": "review-contact@example.com" }
}

View file

@ -0,0 +1,138 @@
// crt-ship config for one game. Everything game-specific lives here; credentials NEVER do (they go in env vars or
// .crt-ship/credentials.json, which must stay gitignored). Paths are relative to this file.
// The type import is optional: it only works when crt-ship is installed in the project (bun add -d <repo url>).
import type { ShipConfig } from 'crt-ship';
const config: ShipConfig = {
app: {
name: 'Example Game',
copyright: '2026 Example Studio',
privacyPolicyUrl: 'https://example.com/privacy',
supportUrl: 'https://example.com',
marketingUrl: 'https://example.com',
// Developer website on Play: the same domain must serve /app-ads.txt if the game shows ads
website: 'https://example.com',
contactEmail: 'support@example.com',
},
// id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale
languages: [
{ id: 'en', play: 'en-US', apple: 'en-US' },
{ id: 'tr', play: 'tr-TR', apple: 'tr' },
{ id: 'es', play: ['es-ES', 'es-419'], apple: 'es-ES' },
{ id: 'ja', play: 'ja-JP', apple: 'ja' },
{ id: 'ar', play: 'ar', apple: 'ar-SA', rtl: true },
],
play: {
packageName: 'com.example.game',
defaultLanguage: 'en-US',
listing: 'store/listing.md',
icon: 'store/icons/play-512.png',
},
apple: {
bundleId: 'com.example.game',
// appId: '1234567890', // Apple ID of the app record; looked up by bundle id when left out
listing: 'store/appstore.md',
reviewShots: 'store/iap-review',
info: {
categories: { primary: 'GAMES', primarySubOne: 'GAMES_CASUAL', primarySubTwo: 'GAMES_PUZZLE', secondary: 'ENTERTAINMENT' },
ageRating: { advertising: true }, // everything else NONE / false
contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT',
free: true,
reviewNotes: [
'Single-player game. No account or login is needed; there is no user-generated content or chat.',
'In-app purchases: Home > Shop. Restore Purchases is in Settings.',
'Ads: optional rewarded videos and interstitials between rounds. The App Tracking Transparency prompt is shown before the ad SDK starts.',
'Privacy policy: Settings > Privacy.',
].join('\n\n'),
},
},
// Same ids on both stores. Prices: USD base; Play converts per region, Apple equalizes from the USA. Overrides set a
// region by hand (Apple needs an existing price point with exactly that value).
products: [
{
id: 'com.example.game.remove_ads', type: 'non_consumable', usd: 2.99,
overrides: [{ region: 'TR', currency: 'TRY', price: 99.99 }],
listings: {
en: { title: 'Remove Ads', description: 'Removes interstitial ads for good.' },
tr: { title: 'Reklamları Kaldır', description: 'Ara reklamları kalıcı olarak kaldırır.' },
},
reviewScreenshot: 'store/iap-review/shop.png',
},
{
id: 'com.example.game.coins_small', type: 'consumable', usd: 0.99,
listings: {
en: { title: '500 Coins', description: 'A small bag of coins.' },
tr: { title: '500 Altın', description: 'Küçük bir kese altın.' },
},
reviewScreenshot: 'store/iap-review/shop.png',
},
],
// Google Play Data safety. Types not listed = not collected. Purposes: functionality, analytics, communications,
// fraud, advertising, personalization, account. Typical F2P game with an analytics SDK + ad mediation:
dataSafety: {
collectsData: true,
encryptedInTransit: true,
deletionRequest: true, // e.g. "email us"; no accounts, so no account deletion URL needed
accountCreation: ['PSL_ACM_NONE'],
data: {
PSL_APPROX_LOCATION: { shared: true, collect: ['analytics', 'advertising', 'fraud'], share: ['analytics', 'advertising', 'fraud'] },
PSL_USER_INTERACTION: { shared: true, collect: ['analytics', 'advertising'], share: ['analytics', 'advertising'] },
PSL_OTHER_APP_ACTIVITY: { shared: false, collect: ['functionality'] },
PSL_PERFORMANCE_DIAGNOSTICS: { shared: true, collect: ['analytics', 'advertising', 'fraud'], share: ['analytics', 'advertising', 'fraud'] },
PSL_DEVICE_ID: { shared: true, collect: ['functionality', 'analytics', 'advertising', 'fraud'], share: ['analytics', 'advertising', 'fraud'] },
PSL_PURCHASE_HISTORY: { shared: false, collect: ['functionality', 'fraud'] },
},
},
renders: 'store/renders',
shots: {
copy: 'store/shots/copy.json',
frames: 'store/shots/frames', // <lang>/phone/<name>.png, <lang>/tablet/<name>.png
out: 'store/renders',
logo: 'store/shots/logo.png', // optional: hero scene + feature graphic
fonts: { title: 'Lilita One', body: 'Montserrat' },
// phone: { w: 440, h: 902, dpr: 3 }, // raw frame size in CSS px (screen below a 54 px status bar)
scenes: [
{ id: 'hero', palette: 'fire', hero: true, over: 34, frames: [{ src: 'hero.png', rotate: -4 }],
badges: [{ y: 0.42, side: 'l', icon: 'flag', iconBg: 'red', rot: -4 }, { y: 0.66, side: 'r', icon: 'offline', iconBg: 'red', rot: 3 }] },
{ id: 'play', palette: 'violet', frames: [{ src: 'gameplay.png', rotate: 3 }],
badges: [{ y: 0.3, side: 'l', icon: 'bolt', iconBg: 'gold', rot: -3 }, { y: 0.7, side: 'r', hot: true, rot: 4 }] },
{ id: 'levels', palette: 'sky', frames: [{ src: 'level-b.png', scale: 0.82 }, { src: 'level-a.png', x: -31, dy: 10, scale: 0.7, rotate: -8 }, { src: 'level-c.png', x: 31, dy: 10, scale: 0.7, rotate: 8 }],
badges: [{ y: 0.66, side: 'r', icon: 'map', iconBg: 'blue', rot: 3 }] },
{ id: 'stars', palette: 'gold', frames: [{ src: 'results.png', rotate: -3 }],
badges: [{ y: 0.42, side: 'l', stars: true, rot: -3 }] },
],
feature: { frame: 'hero.png', palette: 'fire' },
capture: {
baseUrl: 'http://127.0.0.1:5173',
query: 'lang={lang}&noads=1&seed=1',
shots: {
'hero': { path: '/?level=12', waitFor: "window.game && game.state === 'playing'", delayMs: 1500, device: 'both' },
'gameplay': { path: '/?level=3', delayMs: 2500, device: 'both' },
'level-a': { path: '/?level=20', delayMs: 2000 },
'level-b': { path: '/?level=40', delayMs: 2000 },
'level-c': { path: '/?level=60', delayMs: 2000 },
'results': { path: '/?level=5&result=3', delayMs: 2000, device: 'both' },
},
},
},
icons: {
composite: 'store/icon/icon.png', // 1024 opaque
foreground: 'store/icon/foreground.png', // 1024 RGBA, subject on transparent
background: 'store/icon/background.png', // 1024 opaque
out: 'store/icons',
web: 'public/icons',
// capacitorAssets: 'native/assets',
// capacitorCommand: 'bunx @capacitor/assets generate --assetPath native/assets',
// androidRes: 'native/android/app/src/main/res',
},
};
export default config;

765
templates/data-safety.csv Normal file
View file

@ -0,0 +1,765 @@
Question ID (machine readable),Response ID (machine readable),Response value,Answer requirement,Human-friendly question label
PSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA,,TRUE,REQUIRED,Does your app collect or share any of the required user data types?
PSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT,,TRUE,MAYBE_REQUIRED,Is all of the user data collected by your app encrypted in transit?
PSL_DATA_COLLECTION_USER_REQUEST_DELETE,,TRUE,MAYBE_REQUIRED,Do you provide a way for users to request that their data is deleted?
PSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_NONE,TRUE,MULTIPLE_CHOICE,Account creation / PSL_ACM_NONE
PSL_DATA_TYPES_PERSONAL,PSL_NAME,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_NAME
PSL_DATA_TYPES_PERSONAL,PSL_EMAIL,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_EMAIL
PSL_DATA_TYPES_PERSONAL,PSL_USER_ACCOUNT,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_USER_ACCOUNT
PSL_DATA_TYPES_PERSONAL,PSL_ADDRESS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_ADDRESS
PSL_DATA_TYPES_PERSONAL,PSL_PHONE,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_PHONE
PSL_DATA_TYPES_PERSONAL,PSL_RACE_ETHNICITY,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_RACE_ETHNICITY
PSL_DATA_TYPES_PERSONAL,PSL_POLITICAL_RELIGIOUS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_POLITICAL_RELIGIOUS
PSL_DATA_TYPES_PERSONAL,PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY
PSL_DATA_TYPES_PERSONAL,PSL_OTHER_PERSONAL,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PERSONAL / PSL_OTHER_PERSONAL
PSL_DATA_TYPES_FINANCIAL,PSL_PURCHASE_HISTORY,TRUE,MULTIPLE_CHOICE,PSL_DATA_TYPES_FINANCIAL / PSL_PURCHASE_HISTORY
PSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_SCORE,,MULTIPLE_CHOICE,PSL_DATA_TYPES_FINANCIAL / PSL_CREDIT_SCORE
PSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER,,MULTIPLE_CHOICE,PSL_DATA_TYPES_FINANCIAL / PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER
PSL_DATA_TYPES_FINANCIAL,PSL_OTHER,,MULTIPLE_CHOICE,PSL_DATA_TYPES_FINANCIAL / PSL_OTHER
PSL_DATA_TYPES_LOCATION,PSL_APPROX_LOCATION,TRUE,MULTIPLE_CHOICE,PSL_DATA_TYPES_LOCATION / PSL_APPROX_LOCATION
PSL_DATA_TYPES_LOCATION,PSL_PRECISE_LOCATION,,MULTIPLE_CHOICE,PSL_DATA_TYPES_LOCATION / PSL_PRECISE_LOCATION
PSL_DATA_TYPES_SEARCH_AND_BROWSING,PSL_WEB_BROWSING_HISTORY,,MULTIPLE_CHOICE,PSL_DATA_TYPES_SEARCH_AND_BROWSING / PSL_WEB_BROWSING_HISTORY
PSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_EMAILS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_EMAIL_AND_TEXT / PSL_EMAILS
PSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_SMS_CALL_LOG,,MULTIPLE_CHOICE,PSL_DATA_TYPES_EMAIL_AND_TEXT / PSL_SMS_CALL_LOG
PSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_OTHER_MESSAGES,,MULTIPLE_CHOICE,PSL_DATA_TYPES_EMAIL_AND_TEXT / PSL_OTHER_MESSAGES
PSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_PHOTOS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PHOTOS_AND_VIDEOS / PSL_PHOTOS
PSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_VIDEOS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_PHOTOS_AND_VIDEOS / PSL_VIDEOS
PSL_DATA_TYPES_AUDIO,PSL_AUDIO,,MULTIPLE_CHOICE,PSL_DATA_TYPES_AUDIO / PSL_AUDIO
PSL_DATA_TYPES_AUDIO,PSL_MUSIC,,MULTIPLE_CHOICE,PSL_DATA_TYPES_AUDIO / PSL_MUSIC
PSL_DATA_TYPES_AUDIO,PSL_OTHER_AUDIO,,MULTIPLE_CHOICE,PSL_DATA_TYPES_AUDIO / PSL_OTHER_AUDIO
PSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_HEALTH,,MULTIPLE_CHOICE,PSL_DATA_TYPES_HEALTH_AND_FITNESS / PSL_HEALTH
PSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_FITNESS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_HEALTH_AND_FITNESS / PSL_FITNESS
PSL_DATA_TYPES_CONTACTS,PSL_CONTACTS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_CONTACTS / PSL_CONTACTS
PSL_DATA_TYPES_CALENDAR,PSL_CALENDAR,,MULTIPLE_CHOICE,PSL_DATA_TYPES_CALENDAR / PSL_CALENDAR
PSL_DATA_TYPES_APP_PERFORMANCE,PSL_CRASH_LOGS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_PERFORMANCE / PSL_CRASH_LOGS
PSL_DATA_TYPES_APP_PERFORMANCE,PSL_PERFORMANCE_DIAGNOSTICS,TRUE,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_PERFORMANCE / PSL_PERFORMANCE_DIAGNOSTICS
PSL_DATA_TYPES_APP_PERFORMANCE,PSL_OTHER_PERFORMANCE,,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_PERFORMANCE / PSL_OTHER_PERFORMANCE
PSL_DATA_TYPES_FILES_AND_DOCS,PSL_FILES_AND_DOCS,,MULTIPLE_CHOICE,PSL_DATA_TYPES_FILES_AND_DOCS / PSL_FILES_AND_DOCS
PSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_INTERACTION,TRUE,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_ACTIVITY / PSL_USER_INTERACTION
PSL_DATA_TYPES_APP_ACTIVITY,PSL_IN_APP_SEARCH_HISTORY,,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_ACTIVITY / PSL_IN_APP_SEARCH_HISTORY
PSL_DATA_TYPES_APP_ACTIVITY,PSL_APPS_ON_DEVICE,,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_ACTIVITY / PSL_APPS_ON_DEVICE
PSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_GENERATED_CONTENT,,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_ACTIVITY / PSL_USER_GENERATED_CONTENT
PSL_DATA_TYPES_APP_ACTIVITY,PSL_OTHER_APP_ACTIVITY,TRUE,MULTIPLE_CHOICE,PSL_DATA_TYPES_APP_ACTIVITY / PSL_OTHER_APP_ACTIVITY
PSL_DATA_TYPES_IDENTIFIERS,PSL_DEVICE_ID,TRUE,MULTIPLE_CHOICE,PSL_DATA_TYPES_IDENTIFIERS / PSL_DEVICE_ID
PSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_NAME) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_NAME) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_NAME) / Required
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_NAME) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_EMAIL) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_EMAIL) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_EMAIL) / Required
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAIL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_USER_ACCOUNT) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Required
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_ADDRESS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_ADDRESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_PHONE) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_PHONE) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_PHONE) / Required
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHONE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_RACE_ETHNICITY) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Required
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Required
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_OTHER_PERSONAL) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Required
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (PSL_PURCHASE_HISTORY) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Required
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_CREDIT_SCORE) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Required
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Required
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_OTHER) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER) / Required
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (PSL_APPROX_LOCATION) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Required
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_PRECISE_LOCATION) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Required
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Required
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_EMAILS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_EMAILS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_EMAILS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_EMAILS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_SMS_CALL_LOG) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Required
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_OTHER_MESSAGES) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Required
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_PHOTOS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PHOTOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_VIDEOS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_VIDEOS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_AUDIO) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_AUDIO) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_AUDIO) / Required
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_MUSIC) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_MUSIC) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_MUSIC) / Required
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_MUSIC) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_OTHER_AUDIO) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Required
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_HEALTH) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_HEALTH) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_HEALTH) / Required
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_HEALTH) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_FITNESS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_FITNESS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_FITNESS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_FITNESS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_CONTACTS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CONTACTS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_CALENDAR) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Required
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CALENDAR) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_CRASH_LOGS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_OTHER_PERFORMANCE) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Required
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_FILES_AND_DOCS) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Required
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (PSL_USER_INTERACTION) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Required
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Required
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_APPS_ON_DEVICE) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Required
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Required
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Required
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collected
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Shared
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (PSL_DEVICE_ID) / Processed ephemerally
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Optional
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Required
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Collection purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
1 Question ID (machine readable) Response ID (machine readable) Response value Answer requirement Human-friendly question label
2 PSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA TRUE REQUIRED Does your app collect or share any of the required user data types?
3 PSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT TRUE MAYBE_REQUIRED Is all of the user data collected by your app encrypted in transit?
4 PSL_DATA_COLLECTION_USER_REQUEST_DELETE TRUE MAYBE_REQUIRED Do you provide a way for users to request that their data is deleted?
5 PSL_SUPPORTED_ACCOUNT_CREATION_METHODS PSL_ACM_NONE TRUE MULTIPLE_CHOICE Account creation / PSL_ACM_NONE
6 PSL_DATA_TYPES_PERSONAL PSL_NAME MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_NAME
7 PSL_DATA_TYPES_PERSONAL PSL_EMAIL MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_EMAIL
8 PSL_DATA_TYPES_PERSONAL PSL_USER_ACCOUNT MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_USER_ACCOUNT
9 PSL_DATA_TYPES_PERSONAL PSL_ADDRESS MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_ADDRESS
10 PSL_DATA_TYPES_PERSONAL PSL_PHONE MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_PHONE
11 PSL_DATA_TYPES_PERSONAL PSL_RACE_ETHNICITY MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_RACE_ETHNICITY
12 PSL_DATA_TYPES_PERSONAL PSL_POLITICAL_RELIGIOUS MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_POLITICAL_RELIGIOUS
13 PSL_DATA_TYPES_PERSONAL PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY
14 PSL_DATA_TYPES_PERSONAL PSL_OTHER_PERSONAL MULTIPLE_CHOICE PSL_DATA_TYPES_PERSONAL / PSL_OTHER_PERSONAL
15 PSL_DATA_TYPES_FINANCIAL PSL_PURCHASE_HISTORY TRUE MULTIPLE_CHOICE PSL_DATA_TYPES_FINANCIAL / PSL_PURCHASE_HISTORY
16 PSL_DATA_TYPES_FINANCIAL PSL_CREDIT_SCORE MULTIPLE_CHOICE PSL_DATA_TYPES_FINANCIAL / PSL_CREDIT_SCORE
17 PSL_DATA_TYPES_FINANCIAL PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER MULTIPLE_CHOICE PSL_DATA_TYPES_FINANCIAL / PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER
18 PSL_DATA_TYPES_FINANCIAL PSL_OTHER MULTIPLE_CHOICE PSL_DATA_TYPES_FINANCIAL / PSL_OTHER
19 PSL_DATA_TYPES_LOCATION PSL_APPROX_LOCATION TRUE MULTIPLE_CHOICE PSL_DATA_TYPES_LOCATION / PSL_APPROX_LOCATION
20 PSL_DATA_TYPES_LOCATION PSL_PRECISE_LOCATION MULTIPLE_CHOICE PSL_DATA_TYPES_LOCATION / PSL_PRECISE_LOCATION
21 PSL_DATA_TYPES_SEARCH_AND_BROWSING PSL_WEB_BROWSING_HISTORY MULTIPLE_CHOICE PSL_DATA_TYPES_SEARCH_AND_BROWSING / PSL_WEB_BROWSING_HISTORY
22 PSL_DATA_TYPES_EMAIL_AND_TEXT PSL_EMAILS MULTIPLE_CHOICE PSL_DATA_TYPES_EMAIL_AND_TEXT / PSL_EMAILS
23 PSL_DATA_TYPES_EMAIL_AND_TEXT PSL_SMS_CALL_LOG MULTIPLE_CHOICE PSL_DATA_TYPES_EMAIL_AND_TEXT / PSL_SMS_CALL_LOG
24 PSL_DATA_TYPES_EMAIL_AND_TEXT PSL_OTHER_MESSAGES MULTIPLE_CHOICE PSL_DATA_TYPES_EMAIL_AND_TEXT / PSL_OTHER_MESSAGES
25 PSL_DATA_TYPES_PHOTOS_AND_VIDEOS PSL_PHOTOS MULTIPLE_CHOICE PSL_DATA_TYPES_PHOTOS_AND_VIDEOS / PSL_PHOTOS
26 PSL_DATA_TYPES_PHOTOS_AND_VIDEOS PSL_VIDEOS MULTIPLE_CHOICE PSL_DATA_TYPES_PHOTOS_AND_VIDEOS / PSL_VIDEOS
27 PSL_DATA_TYPES_AUDIO PSL_AUDIO MULTIPLE_CHOICE PSL_DATA_TYPES_AUDIO / PSL_AUDIO
28 PSL_DATA_TYPES_AUDIO PSL_MUSIC MULTIPLE_CHOICE PSL_DATA_TYPES_AUDIO / PSL_MUSIC
29 PSL_DATA_TYPES_AUDIO PSL_OTHER_AUDIO MULTIPLE_CHOICE PSL_DATA_TYPES_AUDIO / PSL_OTHER_AUDIO
30 PSL_DATA_TYPES_HEALTH_AND_FITNESS PSL_HEALTH MULTIPLE_CHOICE PSL_DATA_TYPES_HEALTH_AND_FITNESS / PSL_HEALTH
31 PSL_DATA_TYPES_HEALTH_AND_FITNESS PSL_FITNESS MULTIPLE_CHOICE PSL_DATA_TYPES_HEALTH_AND_FITNESS / PSL_FITNESS
32 PSL_DATA_TYPES_CONTACTS PSL_CONTACTS MULTIPLE_CHOICE PSL_DATA_TYPES_CONTACTS / PSL_CONTACTS
33 PSL_DATA_TYPES_CALENDAR PSL_CALENDAR MULTIPLE_CHOICE PSL_DATA_TYPES_CALENDAR / PSL_CALENDAR
34 PSL_DATA_TYPES_APP_PERFORMANCE PSL_CRASH_LOGS MULTIPLE_CHOICE PSL_DATA_TYPES_APP_PERFORMANCE / PSL_CRASH_LOGS
35 PSL_DATA_TYPES_APP_PERFORMANCE PSL_PERFORMANCE_DIAGNOSTICS TRUE MULTIPLE_CHOICE PSL_DATA_TYPES_APP_PERFORMANCE / PSL_PERFORMANCE_DIAGNOSTICS
36 PSL_DATA_TYPES_APP_PERFORMANCE PSL_OTHER_PERFORMANCE MULTIPLE_CHOICE PSL_DATA_TYPES_APP_PERFORMANCE / PSL_OTHER_PERFORMANCE
37 PSL_DATA_TYPES_FILES_AND_DOCS PSL_FILES_AND_DOCS MULTIPLE_CHOICE PSL_DATA_TYPES_FILES_AND_DOCS / PSL_FILES_AND_DOCS
38 PSL_DATA_TYPES_APP_ACTIVITY PSL_USER_INTERACTION TRUE MULTIPLE_CHOICE PSL_DATA_TYPES_APP_ACTIVITY / PSL_USER_INTERACTION
39 PSL_DATA_TYPES_APP_ACTIVITY PSL_IN_APP_SEARCH_HISTORY MULTIPLE_CHOICE PSL_DATA_TYPES_APP_ACTIVITY / PSL_IN_APP_SEARCH_HISTORY
40 PSL_DATA_TYPES_APP_ACTIVITY PSL_APPS_ON_DEVICE MULTIPLE_CHOICE PSL_DATA_TYPES_APP_ACTIVITY / PSL_APPS_ON_DEVICE
41 PSL_DATA_TYPES_APP_ACTIVITY PSL_USER_GENERATED_CONTENT MULTIPLE_CHOICE PSL_DATA_TYPES_APP_ACTIVITY / PSL_USER_GENERATED_CONTENT
42 PSL_DATA_TYPES_APP_ACTIVITY PSL_OTHER_APP_ACTIVITY TRUE MULTIPLE_CHOICE PSL_DATA_TYPES_APP_ACTIVITY / PSL_OTHER_APP_ACTIVITY
43 PSL_DATA_TYPES_IDENTIFIERS PSL_DEVICE_ID TRUE MULTIPLE_CHOICE PSL_DATA_TYPES_IDENTIFIERS / PSL_DEVICE_ID
44 PSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collected
45 PSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Shared
46 PSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_NAME) / Processed ephemerally
47 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_NAME) / Optional
48 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_NAME) / Required
49 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collection purpose
50 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collection purpose
51 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collection purpose
52 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collection purpose
53 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collection purpose
54 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collection purpose
55 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Collection purpose
56 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Sharing purpose
57 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Sharing purpose
58 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Sharing purpose
59 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Sharing purpose
60 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Sharing purpose
61 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Sharing purpose
62 PSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_NAME) / Sharing purpose
63 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collected
64 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Shared
65 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_EMAIL) / Processed ephemerally
66 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_EMAIL) / Optional
67 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_EMAIL) / Required
68 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collection purpose
69 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collection purpose
70 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collection purpose
71 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collection purpose
72 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collection purpose
73 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collection purpose
74 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Collection purpose
75 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Sharing purpose
76 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Sharing purpose
77 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Sharing purpose
78 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Sharing purpose
79 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Sharing purpose
80 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Sharing purpose
81 PSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_EMAIL) / Sharing purpose
82 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collected
83 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Shared
84 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_USER_ACCOUNT) / Processed ephemerally
85 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Optional
86 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Required
87 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
88 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
89 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
90 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
91 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
92 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
93 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose
94 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
95 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
96 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
97 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
98 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
99 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
100 PSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose
101 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collected
102 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Shared
103 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_ADDRESS) / Processed ephemerally
104 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_ADDRESS) / Optional
105 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_ADDRESS) / Required
106 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collection purpose
107 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collection purpose
108 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collection purpose
109 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collection purpose
110 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collection purpose
111 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collection purpose
112 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Collection purpose
113 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Sharing purpose
114 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Sharing purpose
115 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Sharing purpose
116 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Sharing purpose
117 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Sharing purpose
118 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Sharing purpose
119 PSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_ADDRESS) / Sharing purpose
120 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collected
121 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Shared
122 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_PHONE) / Processed ephemerally
123 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_PHONE) / Optional
124 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_PHONE) / Required
125 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collection purpose
126 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collection purpose
127 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collection purpose
128 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collection purpose
129 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collection purpose
130 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collection purpose
131 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Collection purpose
132 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Sharing purpose
133 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Sharing purpose
134 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Sharing purpose
135 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Sharing purpose
136 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Sharing purpose
137 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Sharing purpose
138 PSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PHONE) / Sharing purpose
139 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collected
140 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Shared
141 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_RACE_ETHNICITY) / Processed ephemerally
142 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Optional
143 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Required
144 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
145 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
146 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
147 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
148 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
149 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
150 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Collection purpose
151 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
152 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
153 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
154 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
155 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
156 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
157 PSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose
158 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collected
159 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Shared
160 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Processed ephemerally
161 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Optional
162 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Required
163 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
164 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
165 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
166 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
167 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
168 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
169 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Collection purpose
170 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
171 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
172 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
173 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
174 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
175 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
176 PSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_POLITICAL_RELIGIOUS) / Sharing purpose
177 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collected
178 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Shared
179 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Processed ephemerally
180 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Optional
181 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Required
182 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
183 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
184 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
185 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
186 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
187 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
188 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose
189 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
190 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
191 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
192 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
193 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
194 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
195 PSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Sharing purpose
196 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collected
197 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Shared
198 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_OTHER_PERSONAL) / Processed ephemerally
199 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Optional
200 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Required
201 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
202 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
203 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
204 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
205 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
206 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
207 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Collection purpose
208 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
209 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
210 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
211 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
212 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
213 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
214 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERSONAL) / Sharing purpose
215 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collected
216 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Shared
217 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_EPHEMERAL FALSE MAYBE_REQUIRED Data usage and handling (PSL_PURCHASE_HISTORY) / Processed ephemerally
218 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Optional
219 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED TRUE SINGLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Required
220 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
221 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
222 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
223 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
224 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
225 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
226 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Collection purpose
227 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
228 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
229 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
230 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
231 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
232 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
233 PSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PURCHASE_HISTORY) / Sharing purpose
234 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collected
235 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Shared
236 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_CREDIT_SCORE) / Processed ephemerally
237 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Optional
238 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Required
239 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
240 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
241 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
242 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
243 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
244 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
245 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Collection purpose
246 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
247 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
248 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
249 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
250 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
251 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
252 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose
253 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collected
254 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Shared
255 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Processed ephemerally
256 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Optional
257 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Required
258 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
259 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
260 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
261 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
262 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
263 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
264 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Collection purpose
265 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
266 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
267 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
268 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
269 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
270 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
271 PSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER) / Sharing purpose
272 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collected
273 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Shared
274 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_OTHER) / Processed ephemerally
275 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_OTHER) / Optional
276 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_OTHER) / Required
277 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collection purpose
278 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collection purpose
279 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collection purpose
280 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collection purpose
281 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collection purpose
282 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collection purpose
283 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Collection purpose
284 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Sharing purpose
285 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Sharing purpose
286 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Sharing purpose
287 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Sharing purpose
288 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Sharing purpose
289 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Sharing purpose
290 PSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER) / Sharing purpose
291 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collected
292 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Shared
293 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_EPHEMERAL FALSE MAYBE_REQUIRED Data usage and handling (PSL_APPROX_LOCATION) / Processed ephemerally
294 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Optional
295 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED TRUE SINGLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Required
296 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
297 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
298 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
299 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
300 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
301 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
302 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Collection purpose
303 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
304 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
305 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
306 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
307 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
308 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
309 PSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_APPROX_LOCATION) / Sharing purpose
310 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collected
311 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Shared
312 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_PRECISE_LOCATION) / Processed ephemerally
313 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Optional
314 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Required
315 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
316 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
317 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
318 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
319 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
320 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
321 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Collection purpose
322 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
323 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
324 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
325 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
326 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
327 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
328 PSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PRECISE_LOCATION) / Sharing purpose
329 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collected
330 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Shared
331 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Processed ephemerally
332 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Optional
333 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Required
334 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
335 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
336 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
337 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
338 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
339 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
340 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Collection purpose
341 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
342 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
343 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
344 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
345 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
346 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
347 PSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose
348 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collected
349 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Shared
350 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_EMAILS) / Processed ephemerally
351 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_EMAILS) / Optional
352 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_EMAILS) / Required
353 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collection purpose
354 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collection purpose
355 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collection purpose
356 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collection purpose
357 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collection purpose
358 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collection purpose
359 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Collection purpose
360 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Sharing purpose
361 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Sharing purpose
362 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Sharing purpose
363 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Sharing purpose
364 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Sharing purpose
365 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Sharing purpose
366 PSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_EMAILS) / Sharing purpose
367 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collected
368 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Shared
369 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_SMS_CALL_LOG) / Processed ephemerally
370 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Optional
371 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Required
372 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
373 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
374 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
375 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
376 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
377 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
378 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose
379 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
380 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
381 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
382 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
383 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
384 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
385 PSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_SMS_CALL_LOG) / Sharing purpose
386 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collected
387 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Shared
388 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_OTHER_MESSAGES) / Processed ephemerally
389 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Optional
390 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Required
391 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
392 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
393 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
394 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
395 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
396 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
397 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Collection purpose
398 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
399 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
400 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
401 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
402 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
403 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
404 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_MESSAGES) / Sharing purpose
405 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collected
406 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Shared
407 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_PHOTOS) / Processed ephemerally
408 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_PHOTOS) / Optional
409 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_PHOTOS) / Required
410 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collection purpose
411 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collection purpose
412 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collection purpose
413 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collection purpose
414 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collection purpose
415 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collection purpose
416 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Collection purpose
417 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Sharing purpose
418 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Sharing purpose
419 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Sharing purpose
420 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Sharing purpose
421 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Sharing purpose
422 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Sharing purpose
423 PSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PHOTOS) / Sharing purpose
424 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collected
425 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Shared
426 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_VIDEOS) / Processed ephemerally
427 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_VIDEOS) / Optional
428 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_VIDEOS) / Required
429 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collection purpose
430 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collection purpose
431 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collection purpose
432 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collection purpose
433 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collection purpose
434 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collection purpose
435 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Collection purpose
436 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Sharing purpose
437 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Sharing purpose
438 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Sharing purpose
439 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Sharing purpose
440 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Sharing purpose
441 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Sharing purpose
442 PSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_VIDEOS) / Sharing purpose
443 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collected
444 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Shared
445 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_AUDIO) / Processed ephemerally
446 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_AUDIO) / Optional
447 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_AUDIO) / Required
448 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collection purpose
449 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collection purpose
450 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collection purpose
451 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collection purpose
452 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collection purpose
453 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collection purpose
454 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Collection purpose
455 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Sharing purpose
456 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Sharing purpose
457 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Sharing purpose
458 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Sharing purpose
459 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Sharing purpose
460 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Sharing purpose
461 PSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_AUDIO) / Sharing purpose
462 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collected
463 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Shared
464 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_MUSIC) / Processed ephemerally
465 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_MUSIC) / Optional
466 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_MUSIC) / Required
467 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collection purpose
468 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collection purpose
469 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collection purpose
470 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collection purpose
471 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collection purpose
472 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collection purpose
473 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Collection purpose
474 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Sharing purpose
475 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Sharing purpose
476 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Sharing purpose
477 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Sharing purpose
478 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Sharing purpose
479 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Sharing purpose
480 PSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_MUSIC) / Sharing purpose
481 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collected
482 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Shared
483 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_OTHER_AUDIO) / Processed ephemerally
484 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Optional
485 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Required
486 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
487 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
488 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
489 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
490 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
491 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
492 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Collection purpose
493 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
494 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
495 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
496 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
497 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
498 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
499 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_AUDIO) / Sharing purpose
500 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collected
501 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Shared
502 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_HEALTH) / Processed ephemerally
503 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_HEALTH) / Optional
504 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_HEALTH) / Required
505 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collection purpose
506 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collection purpose
507 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collection purpose
508 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collection purpose
509 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collection purpose
510 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collection purpose
511 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Collection purpose
512 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Sharing purpose
513 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Sharing purpose
514 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Sharing purpose
515 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Sharing purpose
516 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Sharing purpose
517 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Sharing purpose
518 PSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_HEALTH) / Sharing purpose
519 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collected
520 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Shared
521 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_FITNESS) / Processed ephemerally
522 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_FITNESS) / Optional
523 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_FITNESS) / Required
524 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collection purpose
525 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collection purpose
526 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collection purpose
527 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collection purpose
528 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collection purpose
529 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collection purpose
530 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Collection purpose
531 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Sharing purpose
532 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Sharing purpose
533 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Sharing purpose
534 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Sharing purpose
535 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Sharing purpose
536 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Sharing purpose
537 PSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_FITNESS) / Sharing purpose
538 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collected
539 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Shared
540 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_CONTACTS) / Processed ephemerally
541 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_CONTACTS) / Optional
542 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_CONTACTS) / Required
543 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collection purpose
544 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collection purpose
545 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collection purpose
546 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collection purpose
547 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collection purpose
548 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collection purpose
549 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Collection purpose
550 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Sharing purpose
551 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Sharing purpose
552 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Sharing purpose
553 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Sharing purpose
554 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Sharing purpose
555 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Sharing purpose
556 PSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CONTACTS) / Sharing purpose
557 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collected
558 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Shared
559 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_CALENDAR) / Processed ephemerally
560 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_CALENDAR) / Optional
561 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_CALENDAR) / Required
562 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collection purpose
563 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collection purpose
564 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collection purpose
565 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collection purpose
566 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collection purpose
567 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collection purpose
568 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Collection purpose
569 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Sharing purpose
570 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Sharing purpose
571 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Sharing purpose
572 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Sharing purpose
573 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Sharing purpose
574 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Sharing purpose
575 PSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CALENDAR) / Sharing purpose
576 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collected
577 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Shared
578 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_CRASH_LOGS) / Processed ephemerally
579 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Optional
580 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Required
581 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
582 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
583 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
584 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
585 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
586 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
587 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Collection purpose
588 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
589 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
590 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
591 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
592 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
593 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
594 PSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_CRASH_LOGS) / Sharing purpose
595 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collected
596 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Shared
597 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_EPHEMERAL FALSE MAYBE_REQUIRED Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Processed ephemerally
598 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Optional
599 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED TRUE SINGLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Required
600 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
601 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
602 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
603 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
604 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
605 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
606 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Collection purpose
607 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
608 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
609 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
610 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
611 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
612 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
613 PSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_PERFORMANCE_DIAGNOSTICS) / Sharing purpose
614 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collected
615 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Shared
616 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_OTHER_PERFORMANCE) / Processed ephemerally
617 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Optional
618 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Required
619 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
620 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
621 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
622 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
623 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
624 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
625 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Collection purpose
626 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
627 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
628 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
629 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
630 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
631 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
632 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_PERFORMANCE) / Sharing purpose
633 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collected
634 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Shared
635 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_FILES_AND_DOCS) / Processed ephemerally
636 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Optional
637 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Required
638 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
639 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
640 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
641 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
642 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
643 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
644 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Collection purpose
645 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
646 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
647 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
648 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
649 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
650 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
651 PSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_FILES_AND_DOCS) / Sharing purpose
652 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collected
653 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Shared
654 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_EPHEMERAL FALSE MAYBE_REQUIRED Data usage and handling (PSL_USER_INTERACTION) / Processed ephemerally
655 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Optional
656 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED TRUE SINGLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Required
657 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
658 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
659 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
660 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
661 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
662 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
663 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Collection purpose
664 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
665 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
666 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
667 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
668 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
669 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
670 PSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_USER_INTERACTION) / Sharing purpose
671 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collected
672 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Shared
673 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Processed ephemerally
674 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Optional
675 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Required
676 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
677 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
678 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
679 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
680 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
681 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
682 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Collection purpose
683 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
684 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
685 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
686 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
687 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
688 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
689 PSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_IN_APP_SEARCH_HISTORY) / Sharing purpose
690 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collected
691 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Shared
692 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_APPS_ON_DEVICE) / Processed ephemerally
693 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Optional
694 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Required
695 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
696 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
697 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
698 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
699 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
700 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
701 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Collection purpose
702 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
703 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
704 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
705 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
706 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
707 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
708 PSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_APPS_ON_DEVICE) / Sharing purpose
709 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collected
710 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Shared
711 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_EPHEMERAL MAYBE_REQUIRED Data usage and handling (PSL_USER_GENERATED_CONTENT) / Processed ephemerally
712 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Optional
713 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED SINGLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Required
714 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
715 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
716 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
717 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
718 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
719 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
720 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collection purpose
721 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
722 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
723 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
724 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
725 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
726 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
727 PSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_USER_GENERATED_CONTENT) / Sharing purpose
728 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collected
729 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Shared
730 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_EPHEMERAL FALSE MAYBE_REQUIRED Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Processed ephemerally
731 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Optional
732 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED TRUE SINGLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Required
733 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
734 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
735 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
736 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
737 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
738 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
739 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Collection purpose
740 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
741 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
742 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
743 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
744 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
745 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
746 PSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_OTHER_APP_ACTIVITY) / Sharing purpose
747 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_COLLECTED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collected
748 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING PSL_DATA_USAGE_ONLY_SHARED TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Shared
749 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_EPHEMERAL FALSE MAYBE_REQUIRED Data usage and handling (PSL_DEVICE_ID) / Processed ephemerally
750 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_OPTIONAL SINGLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Optional
751 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL PSL_DATA_USAGE_USER_CONTROL_REQUIRED TRUE SINGLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Required
752 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE PSL_APP_FUNCTIONALITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collection purpose
753 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collection purpose
754 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collection purpose
755 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE PSL_FRAUD_PREVENTION_SECURITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collection purpose
756 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collection purpose
757 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collection purpose
758 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Collection purpose
759 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE PSL_APP_FUNCTIONALITY MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
760 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE PSL_ANALYTICS TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
761 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE PSL_DEVELOPER_COMMUNICATIONS MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
762 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE PSL_FRAUD_PREVENTION_SECURITY TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
763 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE PSL_ADVERTISING TRUE MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
764 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE PSL_PERSONALIZATION MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Sharing purpose
765 PSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE PSL_ACCOUNT_MANAGEMENT MULTIPLE_CHOICE Data usage and handling (PSL_DEVICE_ID) / Sharing purpose

67
templates/declarations.md Normal file
View file

@ -0,0 +1,67 @@
# Example Game · store declarations (answers to copy into the consoles)
Answers for a typical single-player free-to-play game with ads (mediation) and in-app purchases, no accounts, no chat,
no user-generated content, general audience 13+. Change what does not match your game. Everything marked **UI** has no
API: Play Console > your app > Policy and programs > **App content** (or Dashboard > "Set up your app"), and App Store
Connect for Apple. Data safety (Play) has an API: `crt-ship play data-safety`.
## Google Play · App content (UI)
| declaration | answer |
|---|---|
| Privacy policy | your policy URL (same one in the listing and inside the app) |
| App access | All functionality is available without special access (no login) |
| Ads | Yes, my app contains ads |
| Content rating | IARC questionnaire, below |
| Target audience and content | 13-15, 16-17, 18 and over; "Could your app unintentionally appeal to children?" No (only if true) |
| News app | No |
| COVID-19 contact tracing / status | No |
| Data safety | `crt-ship play data-safety` (then check the summary in the Console) |
| Government apps | No |
| Financial features | My app doesn't provide any financial features |
| Health apps | No health features |
| Advertising ID | Yes, uses it; purposes: Advertising or marketing, Analytics, Fraud prevention / security / compliance |
| Sign-in details (App access) | No sign-in needed |
| Store settings | App category Game > (Casual, Puzzle, Arcade ...); tags; contact email + website (website serves app-ads.txt) |
Families / Designed for Families: only if you really target children; it changes the ad SDK setup (certified networks,
no ad id). If you leave children out, the policy and the ad mediation must say "not directed at children" too.
## IARC content rating (Play UI; Apple has its own form, `crt-ship apple appinfo`)
Category: Game. Typical answers for a non-violent casual game:
| topic | answer |
|---|---|
| Violence (realistic / cartoon / fantasy) | No |
| Blood / gore | No |
| Fear / horror | No |
| Sexuality / nudity | No |
| Language (profanity, crude humour) | No |
| Controlled substances | No |
| Gambling: simulated / real money | No / No |
| Random paid items (loot boxes) | No (fixed contents only; answer Yes if a paid item has random contents) |
| Users interact or exchange content | No (no chat; OS share sheet does not count) |
| Shares location with other users | No |
| Digital purchases | Yes (in-app purchases) |
| Unrestricted internet / browser | No |
Expected: PEGI 3, ESRB Everyone, USK 0, IARC 3+. Apple age rating 4+ (all content questions None; ads are declared as
"advertising: true", which does not change 4+).
## App Store (UI-only parts)
- App record: App Store Connect > Apps > + > New App (platform, name, primary language, bundle id, SKU). No API.
- App Privacy: `app-privacy.md`, then **Publish**.
- Export compliance: put `ITSAppUsesNonExemptEncryption = NO` in Info.plist (only HTTPS / standard encryption) so
every build skips the question.
- First in-app purchases: from the IAP page "Add for Review" into the draft submission with the version (see the
store-iap-setup skill).
- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app** (Settings > Privacy).
## Ads
- app-ads.txt at the root of the developer website named in both listings (`https://example.com/app-ads.txt`),
with the mediation's DIRECT line plus each network's lines.
- iOS: SKAdNetwork ids of every network in Info.plist, ATT prompt before the ad SDK starts.
- EU / EEA / UK / CH: consent (CMP) or non-personalized ads.

62
templates/listing.md Normal file
View file

@ -0,0 +1,62 @@
# Example Game · Google Play listing copy
Source for `crt-ship play listing` (title, short + full description) and `crt-ship play release` (release notes).
Rules the parser follows:
- every `## ...` section with a `<!-- play: <codes> -->` line is a listing; one section can serve several Play codes
(`<!-- play: es-ES, es-419 -->`)
- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and
**what's new**, each followed by one fenced block
- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters
Only claim what is really in the game. No prices, no "best / #1", no other games' names.
---
## English (en-US)
<!-- play: en-US -->
### Play short description (max 80)
```
Short, punchy promise of the core loop in one line
```
### Play full description (max 4000)
```
First paragraph: what the player does and why it feels good, in two or three sentences.
FEATURES
• One-thumb controls
• 100+ levels and new ones every update
• Play offline, no account needed
Free to play. Contains ads and offers optional in-app purchases.
```
### What's new (max 500)
```
First release. Have fun!
```
## Türkçe (tr-TR)
<!-- play: tr-TR -->
### Play kısa açıklama · short (max 80)
```
Oyunun özünü anlatan kısa ve vurucu bir cümle
```
### Play tam açıklama · full (max 4000)
```
İlk paragraf: oyuncu ne yapıyor ve neden keyifli, iki üç cümlede.
ÖZELLİKLER
• Tek parmakla kontrol
• 100+ bölüm, her güncellemede yenileri
• İnternetsiz oyna, hesap gerekmez
Ücretsiz oyun. Reklam içerir ve isteğe bağlı uygulama içi satın alımlar sunar.
```
### Yenilikler · what's new (max 500)
```
İlk sürüm. İyi eğlenceler!
```

View file

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Privacy Policy · Example Studio</title>
<!--
One studio-wide privacy policy for every game, with a per-game services table. Host it at a stable URL
(https://example.com/privacy) and use that URL in both store listings, in crt-ship.config.ts and inside each game
(Settings > Privacy; App Store guideline 5.1.1). Replace every "Example" value and remove services you do not use.
Update the date and the table whenever a game starts collecting something new, then the store declarations.
This is a template, not legal advice.
-->
<style>
:root { color-scheme: light dark; --fg: #1b1c20; --bg: #ffffff; --muted: #5d6170; --line: #e3e5ea; --accent: #2563eb; }
@media (prefers-color-scheme: dark) { :root { --fg: #eceef3; --bg: #15161a; --muted: #a3a8b6; --line: #2b2d35; --accent: #7aa2ff; } }
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
main { max-width: 760px; margin: 0 auto; padding: 32px 16px 64px; }
h1 { font-size: 1.8rem; margin: 0 0 4px; } h2 { font-size: 1.2rem; margin-top: 2em; }
p.meta { color: var(--muted); margin: 0 0 24px; }
a { color: var(--accent); }
.table { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: .95rem; }
th, td { text-align: left; vertical-align: top; padding: 8px 10px; border-bottom: 1px solid var(--line); }
th { font-weight: 600; }
nav a { margin-right: 12px; }
</style>
</head>
<body>
<main>
<nav><a href="#en">English</a><a href="#tr">Türkçe</a></nav>
<section id="en">
<h1>Privacy Policy</h1>
<p class="meta">Example Studio · applies to all our games · last updated 2026-01-01</p>
<p>We make small games. We collect as little as we can, never sell personal data, and none of our games needs an
account. This page explains what each game collects, why, and what you can do about it.</p>
<h2>What our games use</h2>
<div class="table"><table>
<tr><th>Game</th><th>Service</th><th>What and why</th><th>Your choice</th></tr>
<tr><td>Example Game</td><td>Analytics (e.g. GameAnalytics)</td><td>Random player id, sessions, level events, device model, OS version, country derived from IP: to improve the game.</td><td>Settings &gt; Analytics off</td></tr>
<tr><td>Example Game</td><td>Ads (e.g. a mediation SDK and its networks)</td><td>Advertising id (if you allow it), IP, device info, ad interactions: to show and measure ads.</td><td>iOS: tracking prompt; Android: OS ad settings; buy "Remove Ads"</td></tr>
<tr><td>Example Game</td><td>App stores</td><td>Purchases are handled by Google Play / the App Store; we never see payment details.</td><td>-</td></tr>
</table></div>
<h2>Children</h2>
<p>Our games are for a general audience and are not directed at children under 13. We do not knowingly collect
personal data from children. If you believe a child sent us data, contact us and we will delete it.</p>
<h2>Retention and your rights</h2>
<p>Analytics data is kept for up to 24 months. You can ask us for a copy of your data or to delete it by writing
to <a href="mailto:privacy@example.com">privacy@example.com</a>; we answer within 30 days. Include the game name and,
if you can, the player id shown in Settings.</p>
<h2>Security</h2>
<p>All network traffic of our games uses HTTPS.</p>
<h2>Changes</h2>
<p>When a game starts collecting something new we update this page and its date first.</p>
<h2>Contact</h2>
<p>Example Studio · <a href="mailto:privacy@example.com">privacy@example.com</a></p>
</section>
<section id="tr" lang="tr">
<h1>Gizlilik Politikası</h1>
<p class="meta">Example Studio · tüm oyunlarımız için geçerlidir · son güncelleme 2026-01-01</p>
<p>Küçük oyunlar yapıyoruz. Olabildiğince az veri topluyoruz, kişisel verileri asla satmıyoruz ve hiçbir oyunumuz
hesap istemiyor. Yukarıdaki tablo her oyunun neyi neden topladığını gösterir.</p>
<h2>Haklarınız</h2>
<p>Verilerinizin bir kopyasını veya silinmesini <a href="mailto:privacy@example.com">privacy@example.com</a> adresine
yazarak isteyebilirsiniz; 30 gün içinde yanıtlıyoruz.</p>
</section>
</main>
</body>
</html>

32
templates/shots-copy.json Normal file
View file

@ -0,0 +1,32 @@
{
"en": {
"hero": { "title": ["Tap. Match.", "Repeat!"], "badges": [["100+ levels", "and more every update"], ["Play offline", "No account needed"]] },
"play": { "eyebrow": "Play", "title": ["Easy to learn,", "hard to put down"], "sub": "One thumb, short rounds, big satisfying moments.", "badges": [["Quick rounds", "Two minutes each"], ["Great!"]] },
"levels": { "eyebrow": "Worlds", "title": ["Explore new", "worlds"], "sub": "A fresh look and new rules every ten levels.", "badges": [["10 worlds", "A new one every 10 levels"]] },
"stars": { "eyebrow": "Stars", "title": ["Play smart,", "grab 3 stars"], "sub": "Finish with moves to spare and collect every star.", "badges": [["3 stars"]] }
},
"tr": {
"hero": { "title": ["Dokun. Eşleştir.", "Tekrarla!"], "badges": [["100+ bölüm", "her güncellemede yenileri"], ["İnternetsiz", "hesap gerekmez"]] },
"play": { "eyebrow": "Oyna", "title": ["Öğrenmesi kolay,", "bırakması zor"], "sub": "Tek parmak, kısa turlar, büyük keyifli anlar.", "badges": [["Kısa turlar", "Her biri iki dakika"], ["Harika!"]] },
"levels": { "eyebrow": "Dünyalar", "title": ["Yeni dünyaları", "keşfet"], "sub": "Her on bölümde yeni bir görünüm ve yeni kurallar.", "badges": [["10 dünya", "Her 10 bölümde yeni biri"]] },
"stars": { "eyebrow": "Yıldızlar", "title": ["Akıllı oyna,", "3 yıldızı kap"], "sub": "Hamle artırarak bitir, tüm yıldızları topla.", "badges": [["3 yıldız"]] }
},
"es": {
"hero": { "title": ["Toca. Combina.", "¡Repite!"], "badges": [["100+ niveles", "y más en cada versión"], ["Sin conexión", "Sin cuenta"]] },
"play": { "eyebrow": "Juega", "title": ["Fácil de aprender,", "difícil de soltar"], "sub": "Un pulgar, rondas cortas, grandes momentos.", "badges": [["Rondas cortas", "Dos minutos cada una"], ["¡Genial!"]] },
"levels": { "eyebrow": "Mundos", "title": ["Explora", "nuevos mundos"], "sub": "Un aspecto nuevo y nuevas reglas cada diez niveles.", "badges": [["10 mundos", "Uno nuevo cada 10 niveles"]] },
"stars": { "eyebrow": "Estrellas", "title": ["Juega con cabeza,", "logra 3 estrellas"], "sub": "Termina con movimientos de sobra y consigue todas las estrellas.", "badges": [["3 estrellas"]] }
},
"ja": {
"hero": { "title": ["タップして、そろえて、", "もう一回!"], "badges": [["100以上のステージ", "アップデートで追加"], ["オフラインで遊べる", "アカウント不要"]] },
"play": { "eyebrow": "プレイ", "title": ["かんたんなのに", "やめられない"], "sub": "片手で遊べる短いラウンド、気持ちいい瞬間がたくさん。", "badges": [["短いラウンド", "1回2分"], ["すごい!"]] },
"levels": { "eyebrow": "ワールド", "title": ["新しいワールドを", "冒険しよう"], "sub": "10ステージごとに新しい見た目と新しいルール。", "badges": [["10のワールド", "10ステージごとに追加"]] },
"stars": { "eyebrow": "スター", "title": ["かしこく遊んで", "星を3つゲット"], "sub": "手数を残してクリアし、すべての星を集めよう。", "badges": [["星3つ"]] }
},
"ar": {
"hero": { "title": ["انقر. طابق.", "كرّر!"], "badges": [["أكثر من 100 مرحلة", "والمزيد مع كل تحديث"], ["العب دون اتصال", "دون حساب"]] },
"play": { "eyebrow": "العب", "title": ["سهلة التعلّم،", "صعبة التوقف"], "sub": "إبهام واحد، جولات قصيرة، ولحظات ممتعة.", "badges": [["جولات قصيرة", "دقيقتان لكل جولة"], ["رائع!"]] },
"levels": { "eyebrow": "العوالم", "title": ["استكشف", "عوالم جديدة"], "sub": "شكل جديد وقواعد جديدة كل عشر مراحل.", "badges": [["10 عوالم", "عالم جديد كل 10 مراحل"]] },
"stars": { "eyebrow": "النجوم", "title": ["العب بذكاء،", "واجمع 3 نجوم"], "sub": "أنهِ المرحلة بحركات متبقية واجمع كل النجوم.", "badges": [["3 نجوم"]] }
}
}

175
tests/pure.test.ts Normal file
View file

@ -0,0 +1,175 @@
import { describe, expect, test } from 'bun:test';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { parseArgs } from '../cli/lib/args';
import { loadConfig, validateConfig } from '../cli/lib/config';
import { parseAppStoreListing, parsePlayListing } from '../cli/lib/copy';
import { dataSafetyCsv, dataSafetyRows } from '../cli/lib/data-safety';
import { ALPHA3, appleTerritory, fromMoney, nearestPrices, playRegionConfigs, samePrice, toMoney } from '../cli/lib/money';
import { maskExpr } from '../cli/icons';
import { ffmpegQ } from '../cli/shots/jpeg';
import { type CopyFile, copyText, palette, scriptFont, validateCopy } from '../cli/shots/lib';
import { numberedImages } from '../cli/play/images';
const T = join(import.meta.dir, '..', 'templates');
const tpl = (f: string) => readFileSync(join(T, f), 'utf8');
describe('config', () => {
test('template config loads and validates', async () => {
const l = await loadConfig(join(T, 'crt-ship.config.ts'));
expect(l.config.play?.packageName).toBe('com.example.game');
expect(l.resolve('store/x.md')).toBe(join(T, 'store/x.md'));
});
test('reports bad fields', () => {
const e = validateConfig({
app: { name: 'X', privacyPolicyUrl: 'http://insecure', supportUrl: 'https://a.b', website: 'https://a.b', contactEmail: 'nope' },
languages: [{ id: 'en' }, { id: 'en' }],
play: { packageName: 'notapackage' },
products: [{ id: 'bad id!', type: 'gift', usd: 0, listings: { de: { title: '', description: 'x' } }, overrides: [{ region: 'TUR', currency: 'tl', price: 1 }] }],
dataSafety: { collectsData: true, encryptedInTransit: true, deletionRequest: true, data: { DEVICE: { shared: true, collect: [] } } },
});
const text = e.join('\n');
for (const s of ['privacyPolicyUrl', 'contactEmail', 'duplicate "en"', 'packageName', 'products[0].id', 'products[0].type', 'products[0].usd',
'listings.de: not in languages', 'region "TUR"', 'currency "tl"', 'dataSafety.data.DEVICE', 'sharing purposes']) expect(text).toContain(s);
});
test('empty config', () => { expect(validateConfig(null)).toEqual(['config must be an object']); });
});
describe('copy parsing', () => {
test('play template', () => {
const c = parsePlayListing(tpl('listing.md'), 'Example Game');
expect(c.map(x => x.lang)).toEqual(['en-US', 'tr-TR']);
expect(c[0]!.title).toBe('Example Game');
expect(c[1]!.whatsNew).toContain('İlk sürüm');
});
test('play: one section, several codes, explicit title, limits', () => {
const md = '## Español\n<!-- play: es-ES, es-419 -->\n### title\n```\nJuego\n```\n### short\n```\nCorto\n```\n### full\n```\nLargo\n```\n';
const c = parsePlayListing(md, 'X');
expect(c.map(x => [x.lang, x.title])).toEqual([['es-ES', 'Juego'], ['es-419', 'Juego']]);
expect(() => parsePlayListing(md.replace('Corto', 'x'.repeat(81)), 'X')).toThrow('short is 81 > 80');
expect(() => parsePlayListing('## A\n<!-- play: en-US -->\n### short\n```\na\n```\n', 'X')).toThrow('no "### ... full ..." block');
});
test('play: emoji count as one character', () => {
const md = `## A\n<!-- play: en-US -->\n### short\n\`\`\`\n${'é'.repeat(80)}\n\`\`\`\n### full\n\`\`\`\nx\n\`\`\`\n`;
expect(parsePlayListing(md, 'X')[0]!.short.length).toBe(80);
});
test('appstore template', () => {
const c = parseAppStoreListing(tpl('appstore.md'));
expect(Object.keys(c)).toEqual(['en-US', 'tr']);
expect(c['en-US']!.subtitle).toBe('Short hook under the name');
});
test('appstore: missing field, keyword spaces, byte limit', () => {
const sec = (kw: string) => `## de-DE\n### subtitle\n\`\`\`\nA\n\`\`\`\n### promotionalText\n\`\`\`\nB\n\`\`\`\n### keywords\n\`\`\`\n${kw}\n\`\`\`\n### description\n\`\`\`\nD\n\`\`\`\n`;
expect(parseAppStoreListing(sec('a,b'))['de-DE']!.keywords).toBe('a,b');
expect(() => parseAppStoreListing(sec('a, b'))).toThrow('no spaces after commas');
expect(() => parseAppStoreListing(sec('ö'.repeat(60)))).toThrow('bytes > 100');
expect(() => parseAppStoreListing('## fr-FR\n### subtitle\n```\nx\n```\n')).toThrow('fr-FR: missing promotionalText');
});
});
describe('data safety csv', () => {
const ds = { collectsData: true, encryptedInTransit: true, deletionRequest: false,
data: { PSL_DEVICE_ID: { shared: true, collect: ['analytics' as const, 'advertising' as const], share: ['advertising' as const] },
PSL_OTHER_PERSONAL: { shared: false, optional: true, collect: ['functionality' as const] } } };
const rows = dataSafetyRows(ds);
const find = (q: string, r: string) => rows.find(x => x[0] === q && x[1] === r);
test('header + overview + account creation', () => {
expect(rows[0]![0]).toBe('Question ID (machine readable)');
expect(find('PSL_DATA_COLLECTION_USER_REQUEST_DELETE', '')![2]).toBe('FALSE');
expect(find('PSL_SUPPORTED_ACCOUNT_CREATION_METHODS', 'PSL_ACM_NONE')![2]).toBe('TRUE');
});
test('types and purposes', () => {
expect(find('PSL_DATA_TYPES_IDENTIFIERS', 'PSL_DEVICE_ID')![2]).toBe('TRUE');
expect(find('PSL_DATA_TYPES_LOCATION', 'PSL_PRECISE_LOCATION')![2]).toBe('');
const q = (t: string, s: string) => `PSL_DATA_USAGE_RESPONSES:${t}:${s}`;
expect(find(q('PSL_DEVICE_ID', 'DATA_USAGE_COLLECTION_PURPOSE'), 'PSL_ANALYTICS')![2]).toBe('TRUE');
expect(find(q('PSL_DEVICE_ID', 'DATA_USAGE_SHARING_PURPOSE'), 'PSL_ANALYTICS')![2]).toBe('');
expect(find(q('PSL_DEVICE_ID', 'DATA_USAGE_SHARING_PURPOSE'), 'PSL_ADVERTISING')![2]).toBe('TRUE');
expect(find(q('PSL_DEVICE_ID', 'PSL_DATA_USAGE_EPHEMERAL'), '')![2]).toBe('FALSE');
expect(find(q('PSL_OTHER_PERSONAL', 'DATA_USAGE_USER_CONTROL'), 'PSL_DATA_USAGE_USER_CONTROL_OPTIONAL')![2]).toBe('TRUE');
expect(find(q('PSL_OTHER_PERSONAL', 'PSL_DATA_USAGE_COLLECTION_AND_SHARING'), 'PSL_DATA_USAGE_ONLY_SHARED')![2]).toBe('');
expect(find(q('PSL_CONTACTS', 'PSL_DATA_USAGE_EPHEMERAL'), '')![2]).toBe('');
});
test('every row has 5 cells; quoting; unknown type', () => {
const csv = dataSafetyCsv(ds);
expect(csv.endsWith('\n')).toBe(true);
expect(csv).toContain('"Does your app collect or share any of the required user data types?"'.slice(1, -1));
for (const r of rows) expect(r.length).toBe(5);
expect(() => dataSafetyRows({ ...ds, data: { PSL_NOPE: { shared: false, collect: ['analytics'] } } })).toThrow('unknown data type');
});
});
describe('prices', () => {
test('toMoney without float noise', () => {
expect(toMoney('USD', 2.99)).toEqual({ currencyCode: 'USD', units: '2', nanos: 990000000 });
expect(toMoney('TRY', 129.99)).toEqual({ currencyCode: 'TRY', units: '129', nanos: 990000000 });
expect(toMoney('JPY', 300)).toEqual({ currencyCode: 'JPY', units: '300', nanos: 0 });
expect(fromMoney(toMoney('EUR', 0.29))).toBeCloseTo(0.29);
expect(() => toMoney('USD', -1)).toThrow();
});
test('play region configs with overrides', () => {
const conv = { a: { regionCode: 'US', price: toMoney('USD', 2.99) }, b: { regionCode: 'TR', price: toMoney('TRY', 109.99) }, c: { regionCode: 'DE', price: toMoney('EUR', 2.99) } };
const r = playRegionConfigs(conv, [{ region: 'TR', currency: 'TRY', price: 99.99 }]);
expect(r.map(x => x.regionCode)).toEqual(['DE', 'TR', 'US']);
expect(r.find(x => x.regionCode === 'TR')!.price).toEqual(toMoney('TRY', 99.99));
expect(r.every(x => x.availability === 'AVAILABLE')).toBe(true);
expect(() => playRegionConfigs(conv, [{ region: 'XX', currency: 'USD', price: 1 }])).toThrow('region XX');
});
test('apple price matching + territories', () => {
expect(samePrice('2.99', 2.99)).toBe(true);
expect(samePrice('30.0', 30)).toBe(true);
expect(samePrice('2.98', 2.99)).toBe(false);
expect(nearestPrices([1.99, 2.49, 2.99, 3.49], 2.89, 2)).toEqual([2.99, 2.49]);
expect(appleTerritory({ region: 'TR', currency: 'TRY', price: 1 })).toBe('TUR');
expect(appleTerritory({ region: 'GB', currency: 'GBP', price: 1 })).toBe('GBR');
expect(appleTerritory({ region: 'ZZ', territory: 'ZZZ', currency: 'USD', price: 1 })).toBe('ZZZ');
expect(() => appleTerritory({ region: 'ZZ', currency: 'USD', price: 1 })).toThrow('set "territory"');
expect(Object.keys(ALPHA3).length).toBeGreaterThan(150);
for (const [k, v] of Object.entries(ALPHA3)) { expect(k).toMatch(/^[A-Z]{2}$/); expect(v).toMatch(/^[A-Z]{3}$/); }
});
});
describe('screenshots', () => {
const copy = JSON.parse(tpl('shots-copy.json')) as CopyFile;
test('script fonts', () => {
expect(scriptFont('en', 'Hello')).toBeNull();
expect(scriptFont('tr', 'Dokun. Eşleştir.')).toBeNull();
expect(scriptFont('ja', copyText('ja', copy.ja))!.slug).toBe('jp');
expect(scriptFont('ar', copyText('ar', copy.ar))!.slug).toBe('arabic');
expect(scriptFont('ko', '안녕')!.slug).toBe('kr');
expect(scriptFont('zh-Hans', '你好')!.slug).toBe('sc');
expect(scriptFont('zh-Hant', '你好')!.slug).toBe('tc');
expect(scriptFont('ru', 'Привет')!.slug).toBe('cyrgreek');
expect(scriptFont('hi', 'नमस्ते')!.slug).toBe('devanagari');
});
test('template copy fits the template scenes', async () => {
const { config } = await loadConfig(join(T, 'crt-ship.config.ts'));
expect(validateCopy(copy, config.shots!, config.languages.map(l => l.id))).toEqual([]);
const broken = { en: { ...copy.en, play: { ...copy.en!.play!, badges: [['a'], ['b'], ['c']] } } } as CopyFile;
expect(validateCopy(broken, config.shots!, ['en', 'de']).join('\n')).toMatch(/3 badges but the scene has 2 slots[\s\S]*de: no copy/);
});
test('palettes', () => {
expect(palette('gold').ink).toBe('#6b2c00');
expect(() => palette('nope')).toThrow('unknown palette');
});
test('jpeg quality mapping', () => { expect(ffmpegQ(100)).toBe(2); expect(ffmpegQ(92)).toBe(4); expect(ffmpegQ(0)).toBe(31); });
test('numbered images prefer jpeg', () => {
expect(numberedImages('/nonexistent')).toEqual([]);
});
});
describe('icons + args', () => {
test('mask expressions', () => {
expect(maskExpr('circle', 192)).toBe('if(lte(hypot(X-96+0.5,Y-96+0.5),96),255,0)');
expect(maskExpr('squircle', 100)).toContain('22');
});
test('args', () => {
const a = parseArgs(['file.aab', '--production', 'draft', '--dry', '--only=a,b', '--internal']);
expect(a._).toEqual(['file.aab']);
expect(a.get('production')).toBe('draft');
expect(a.has('dry')).toBe(true);
expect(a.list('only')).toEqual(['a', 'b']);
expect(a.get('internal')).toBe('true');
expect(() => parseArgs(['--jobs', 'x']).num('jobs', 1)).toThrow();
});
});

15
tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["bun"],
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["cli", "tests", "templates/crt-ship.config.ts"]
}