build-time-secret-injection
Use when wiring a value that ships in the binary but must stay out of public-repo diffs until launch (AdMob `GADApplicationIdentifier` / banner unit ID, a third-party SDK app key) into an Apple build via xcconfig, Info.plist `$()` substitution and a guarded `Bundle.main` read, in
Install
npx skills add https://github.com/wei18/apple-dev-skills/tree/main/apple-dev-skills/skills/build-time-secret-injection
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wei18-apple-dev-skills@llmmart
git clone https://github.com/wei18/apple-dev-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wei18/apple-dev-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Build-time Secret Injection (Apple-platform)
Tuist assumption: the Project.swift snippets below assume the app's .xcodeproj is generated by Tuist from a root-level Project.swift (Tuist's own convention keeps Tuist/ for Package.swift and shared helpers, not for Project.swift itself). A hand-maintained .xcodeproj needs no Project.swift step — see Non-Tuist projects below for the equivalent (an xcconfig referenced directly from the target's Build Settings → Configurations, instead of via Project.swift).
When to invoke
Any task that introduces or wires values which are:
- Technically app-public once the app ships (embedded in
Info.plist, visible in shipped binary, observable in network traffic), AND - Pre-launch sensitive (committed to public repo before ship = ad-fraud reconnaissance window, convention violation among collaborators, or fingerprinting of unreleased product)
Examples:
- AdMob App ID + Banner / Interstitial / Rewarded Unit IDs
- ASC API
.p8key, key-id, issuer ID, ASC numeric app-id - Any third-party SDK app key (Firebase, RevenueCat, etc.) where the convention is "hold until ship"
Do NOT invoke for:
- True per-deploy secrets (signing certs, CloudKit production API keys, push notification keys) — those have stricter patterns (see
apple-public-repo-security) - Values genuinely public from day 1 (bundle IDs, CKContainer IDs, IAP product IDs, marketing URLs)
The pattern
| Value's consumer | Layer | Storage |
|---|---|---|
Xcode build / Info.plist / Bundle.main read |
Layer 1 — xcconfig | Tuist/<Domain>.xcconfig (gitignored) |
CLI tooling (swift run <CLI>, shell scripts) |
Layer 2 — .env |
secrets/.env (gitignored) |
| Signing certs, CloudKit server-to-server key, APNs key | Not this skill | → apple-public-repo-security |
Two storage layers, one mechanism per layer
Layer 1 — Build-time secrets (consumed by Xcode build process)
Tuist/
├── <Domain>.xcconfig # gitignored, real values
├── <Domain>.xcconfig.example # committed, sandbox values + structure
├── Signing.xcconfig # existing precedent (gitignored)
└── Signing.xcconfig.example # existing precedent (committed)
- xcconfig holds
KEY = VALUEpairs Project.swiftdeclares per-targetsettings(configurations: [.debug(name:, xcconfig:), .release(name:, xcconfig:)])pointing at the file- Info.plist uses
$(KEY)substitution to embed values at compile time — e.g.ADMOB_APP_ID = ca-app-pub-<publisher-id>~<app-id>in the xcconfig and<key>GADApplicationIdentifier</key><string>$(ADMOB_APP_ID)</string>in Info.plist (GADApplicationIdentifieris the key the Google Mobile Ads SDK reads at startup;ADMOB_APP_ID/ADMOB_BANNER_UNIT_IDare this project's own xcconfig names) - App code reads via
Bundle.main.object(forInfoDictionaryKey: "...")— guarded against nil / empty / unresolved$()token - CI side (
ci_scripts/ci_post_clone.sh): reads XCC env vars (stored as Secrets in ASC → Xcode Cloud → Workflow → Environment Variables) and generates the xcconfig file beforetuist generateruns
Layer 2 — CLI tooling secrets (consumed by swift run <CLI> etc.)
secrets/
├── .env # gitignored, real values
├── .env.example # committed, structure + docstring
├── <Domain>AuthKey_*.p8 # gitignored binary cert
└── .gitignore # deny-by-default: */!*.example/!README.md/!example/ /!example/**
.envisKEY=VALUEshell-style- Dev pattern:
source secrets/.env && swift run <CLI> --flag-using-$KEY ... - CLI itself does NOT need code changes to read env automatically
Project.swift wiring (Tuist)
let appTarget = Target.target(
// ...
settings: .settings(
base: ["SWIFT_VERSION": "6"],
configurations: [
.debug(name: "Debug", xcconfig: "Tuist/Config-Debug.xcconfig"),
.release(name: "Release", xcconfig: "Tuist/Config-Release.xcconfig"),
]
)
)
Wrap multiple xcconfigs via a Config-{Debug,Release}.xcconfig that #include? both Signing + AdMob (Tuist's xcconfig: arg takes a single path).
Multi-app dispatch in ci_post_clone.sh
When one repo ships multiple app schemes (e.g. AppA + AppB), XCC sets $CI_PRODUCT and $CI_XCODE_SCHEME per workflow. For the case-switch that picks the right env-var prefix per scheme, read references/multi-app-ci-dispatch.md.
Non-Tuist projects
If the project uses a hand-edited .xcodeproj, the equivalent storage is Config/*.xcconfig referenced via target → Build Settings → Base Configuration. Pattern is otherwise unchanged. Tuist regen / clobbering concerns don't apply; manual sync remains your responsibility.
Smoke test scope (CRITICAL)
The substitution-resolution check must run against the built bundle's Info.plist, not the source-tree Info.plist:
// ❌ WRONG — reads source plist, gets literal "$(ADMOB_BANNER_UNIT_ID)" — passes falsely
// (ADMOB_BANNER_UNIT_ID is this project's own xcconfig key name, not one Google defines —
// see the multi-app xcconfig rendering above.)
let plist = try PropertyListSerialization.propertyList(from: sourceData, ...)
#expect((plist["ADMOB_BANNER_UNIT_ID"] as? String)?.isEmpty == false) // passes for "$(...)" string
// ✅ RIGHT — combine source-plist key-presence test + runtime guard in code
// Source test catches "someone deleted the key"; runtime guard catches "substitution failed"
guard
let bannerID = Bundle.main.object(forInfoDictionaryKey: "ADMOB_BANNER_UNIT_ID") as? String,
!bannerID.isEmpty,
!bannerID.hasPrefix("$(")
else { preconditionFailure("...") }
Consider adding a build-phase script that asserts no $() literals survived substitution into the built .app/Info.plist; until one exists, the runtime guard above is the only catch.
Anti-patterns to refuse
Production IDs in code comments, docstrings, PR descriptions, commit messages, or
Info.plist <!-- -->blocks. Even when the value field uses a sandbox stand-in, the surrounding prose leaks production via git history. Including the literal ID anywhere in tracked text — even prefixed by TODO / FIXME / "will-replace" — IS the leak. Reference the out-of-repo vault entry or the gitignored secrets file by name; never paste the value inline.Hardcoded production IDs in
Live.swiftwith intent to "swap before release" without an enforcement mechanism. The interimfatalError("REPLACE_BEFORE_RELEASE: ...")pattern is acceptable as a TRANSITIONAL guard paired with xcconfig migration, but is forbidden as a long-term standalone solution. Once xcconfig is in place, replace with: Info.plist$()+ runtime guard verifyingBundle.main.object(forInfoDictionaryKey:)returns non-empty AND non-$(...).Conflating GitHub Secrets with XCC env vars. Apple's XCC does not read GH Secrets — they're separate storage. If CI builds on XCC, secrets must live in XCC's Environment Variables UI, not GH.
Most common mistake: ❗ Shell env vars do NOT feed xcconfig
$(VAR)interpolation. xcconfig variable resolution reads from the build settings table, not process env.source admob.env && xcodebuild archivedoes NOT populate$(ADMOB_APP_ID). Only positionalxcodebuild VAR=valueor-xcconfig override.xcconfigactually injects, OR a CI script writes the xcconfig file before build.Bundle.main.object(forInfoDictionaryKey:) as! String— force cast bypasses SwiftLint AND crashes hard if CI generation skipped + xcconfig missing. Useas? String+guard let ... else { preconditionFailure }with the unresolved-$()check.Bundle.mainfrom inside a SwiftPM package is fine for app-target composition root reads but flaky for #Preview / test host / unit-test contexts. Wrap reads in a smoke test that asserts the key exists in source plist; runtime guard compensates for missing-substitution case.secrets/orTuist/<Domain>.xcconfigcommitted by accident. Use an innersecrets/.gitignoredeny-list (* / !*.example / !README.md / !example/ / !example/**— the last two are required, otherwise*ignores theexample/directory and git never descends into it) PLUS root.gitignorerulesTuist/*.xcconfig+!Tuist/*.xcconfig.exampleso neither slips through default-add operations.Tuist
tuist generatesilently clobbering unmanaged xcconfigs. IfTuist/<Domain>.xcconfigexists but is NOT referenced inProject.swift's.settings(configurations:), Tuist regen drops it from the project. Verify Project.swift wiring before assuming xcconfig is active.
Verification checklist
Use this both when adding a new secret value and when auditing an existing implementation.
- Decide the layer: Xcode build / Info.plist /
Bundle.mainread → Layer 1 xcconfig;swift run/ CLI scripts / shell → Layer 2secrets/.env - Root
.gitignorehasTuist/*.xcconfig+!Tuist/*.xcconfig.example;secrets/.gitignoreinner deny-list present (* / !*.example / !README.md / !example/ / !example/**) —git check-ignore -v secrets/example/README.mdreports nothing - KEY is added to the appropriate
.examplefile with a sandbox/test default value, with an inline comment naming the out-of-repo vault entry that holds the real value (password manager / team vault) — never the literal value -
Project.swiftper-target.settings(configurations:)references the xcconfig - Layer 1:
Info.plistuses$(KEY)substitution for each secret; app code reads viaBundle.main.object(forInfoDictionaryKey:)with a guard (NOTas!) that rejectsnil, empty, and the$(...)literal - Smoke test reads the source plist for a key-presence assertion
-
ci_post_clone.shwrites the xcconfig from the XCC env var (${VAR:?missing message}) BEFOREtuist generate; if multi-app,caseon$CI_XCODE_SCHEMEselects per-app env vars — seereferences/multi-app-ci-dispatch.md - XCC Workflow Environment Variables UI lists each KEY (per scheme if multi-app), marked Secret
-
grep -r "<real-prod-value>" .(excluding gitignored dirs) returns zero hits across all tracked files - The real value is recorded in the out-of-repo secret store, noting which entry holds it — never in a tracked file
Related skills
- REQUIRED background:
apple-public-repo-security— broader secret-leak prevention (gitleaks, lefthook, GitHub Secret Scanning) - SIBLING:
monetization-sdk-integration— invoke together when wiring AdMob; this skill is the secret-handling layer - SIBLING:
asc-api-automation— ASC API key handling (the.p8) once the key leaves the build and drives the REST API - Official sources: when verifying or updating a factual or version-sensitive claim, read
references/official-docs.md.
Files (apple-dev-skills)
-
references
-
multi-app-ci-dispatch.md 913 B
# Multi-App CI Dispatch ### Multi-app dispatch in `ci_post_clone.sh` When one repo ships multiple app schemes (e.g. AppA + AppB), XCC sets `$CI_PRODUCT` and `$CI_XCODE_SCHEME` per workflow. Case-switch on the scheme to pick the right env-var prefix: ```bash case "${CI_XCODE_SCHEME:-${CI_PRODUCT:-}}" in AppA) APP_ID="${APP_A_ADMOB_APP_ID:?missing APP_A_ADMOB_APP_ID}" BANNER_UNIT_ID="${APP_A_ADMOB_BANNER_UNIT_ID:?missing APP_A_ADMOB_BANNER_UNIT_ID}" ;; AppB) APP_ID="${APP_B_ADMOB_APP_ID:?missing APP_B_ADMOB_APP_ID}" BANNER_UNIT_ID="${APP_B_ADMOB_BANNER_UNIT_ID:?missing APP_B_ADMOB_BANNER_UNIT_ID}" ;; *) echo "Unknown CI_XCODE_SCHEME: ${CI_XCODE_SCHEME:-}" >&2 exit 1 ;; esac cat > Tuist/AdMob.xcconfig <<EOF ADMOB_APP_ID = ${APP_ID} ADMOB_BANNER_UNIT_ID = ${BANNER_UNIT_ID} EOF ``` Run **before** `tuist generate` so the per-target xcconfig reference resolves. -
official-docs.md 989 B
Official pages backing this skill's claims; read when verifying or updating a factual or version-sensitive claim. | Page | URL | Backs | |---|---|---| | Adding a build configuration file to your project | https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project | `#include?`, `$(VAR)` substitution | | Explore advanced project configuration in Xcode (WWDC21) | https://developer.apple.com/videos/play/wwdc2021/10210/ | Optional includes; build-setting precedence layers | | Writing custom build scripts | https://developer.apple.com/documentation/xcode/writing-custom-build-scripts | `ci_post_clone.sh` | | Environment variable reference | https://developer.apple.com/documentation/xcode/environment-variable-reference | `CI_XCODE_SCHEME` / `CI_PRODUCT`; custom variables are readable from custom build scripts and test actions | | Set up Google Mobile Ads SDK | https://developers.google.com/admob/ios/quick-start | `GADApplicationIdentifier` |
-
-
SKILL.md 11.4 KB
--- name: build-time-secret-injection description: Use when wiring a value that ships in the binary but must stay out of public-repo diffs until launch (AdMob `GADApplicationIdentifier` / banner unit ID, a third-party SDK app key) into an Apple build via xcconfig, Info.plist `$()` substitution and a guarded `Bundle.main` read, including `ci_post_clone.sh` generation on Xcode Cloud; or when CLI tooling reads an ASC `.p8` / key ID from `secrets/.env`. Not for signing certs, CloudKit or APNs keys, nor leak prevention (gitleaks, lefthook, Secret Scanning) — see apple-public-repo-security. SDK isolation is monetization-sdk-integration. --- # Build-time Secret Injection (Apple-platform) **Tuist assumption**: the `Project.swift` snippets below assume the app's `.xcodeproj` is generated by Tuist from a root-level `Project.swift` (Tuist's own convention keeps `Tuist/` for `Package.swift` and shared helpers, not for `Project.swift` itself). A hand-maintained `.xcodeproj` needs no `Project.swift` step — see **Non-Tuist projects** below for the equivalent (an xcconfig referenced directly from the target's Build Settings → Configurations, instead of via `Project.swift`). ## When to invoke Any task that introduces or wires values which are: - Technically **app-public** once the app ships (embedded in `Info.plist`, visible in shipped binary, observable in network traffic), AND - **Pre-launch sensitive** (committed to public repo before ship = ad-fraud reconnaissance window, convention violation among collaborators, or fingerprinting of unreleased product) Examples: - AdMob App ID + Banner / Interstitial / Rewarded Unit IDs - ASC API `.p8` key, key-id, issuer ID, ASC numeric app-id - Any third-party SDK app key (Firebase, RevenueCat, etc.) where the convention is "hold until ship" Do NOT invoke for: - True per-deploy secrets (signing certs, CloudKit production API keys, push notification keys) — those have stricter patterns (see `apple-public-repo-security`) - Values genuinely public from day 1 (bundle IDs, CKContainer IDs, IAP product IDs, marketing URLs) ## The pattern | Value's consumer | Layer | Storage | |---|---|---| | Xcode build / Info.plist / `Bundle.main` read | Layer 1 — xcconfig | `Tuist/<Domain>.xcconfig` (gitignored) | | CLI tooling (`swift run <CLI>`, shell scripts) | Layer 2 — `.env` | `secrets/.env` (gitignored) | | Signing certs, CloudKit server-to-server key, APNs key | Not this skill | → `apple-public-repo-security` | ### Two storage layers, one mechanism per layer **Layer 1 — Build-time secrets (consumed by Xcode build process)** ``` Tuist/ ├── <Domain>.xcconfig # gitignored, real values ├── <Domain>.xcconfig.example # committed, sandbox values + structure ├── Signing.xcconfig # existing precedent (gitignored) └── Signing.xcconfig.example # existing precedent (committed) ``` - xcconfig holds `KEY = VALUE` pairs - `Project.swift` declares per-target `settings(configurations: [.debug(name:, xcconfig:), .release(name:, xcconfig:)])` pointing at the file - Info.plist uses `$(KEY)` substitution to embed values at compile time — e.g. `ADMOB_APP_ID = ca-app-pub-<publisher-id>~<app-id>` in the xcconfig and `<key>GADApplicationIdentifier</key><string>$(ADMOB_APP_ID)</string>` in Info.plist (`GADApplicationIdentifier` is the key the Google Mobile Ads SDK reads at startup; `ADMOB_APP_ID` / `ADMOB_BANNER_UNIT_ID` are this project's own xcconfig names) - App code reads via `Bundle.main.object(forInfoDictionaryKey: "...")` — guarded against nil / empty / unresolved `$()` token - **CI side** (`ci_scripts/ci_post_clone.sh`): reads XCC env vars (stored as Secrets in ASC → Xcode Cloud → Workflow → Environment Variables) and generates the xcconfig file before `tuist generate` runs **Layer 2 — CLI tooling secrets (consumed by `swift run <CLI>` etc.)** ``` secrets/ ├── .env # gitignored, real values ├── .env.example # committed, structure + docstring ├── <Domain>AuthKey_*.p8 # gitignored binary cert └── .gitignore # deny-by-default: */!*.example/!README.md/!example/ /!example/** ``` - `.env` is `KEY=VALUE` shell-style - Dev pattern: `source secrets/.env && swift run <CLI> --flag-using-$KEY ...` - CLI itself does NOT need code changes to read env automatically ### Project.swift wiring (Tuist) ```swift let appTarget = Target.target( // ... settings: .settings( base: ["SWIFT_VERSION": "6"], configurations: [ .debug(name: "Debug", xcconfig: "Tuist/Config-Debug.xcconfig"), .release(name: "Release", xcconfig: "Tuist/Config-Release.xcconfig"), ] ) ) ``` Wrap multiple xcconfigs via a `Config-{Debug,Release}.xcconfig` that `#include?` both Signing + AdMob (Tuist's `xcconfig:` arg takes a single path). ### Multi-app dispatch in `ci_post_clone.sh` When one repo ships multiple app schemes (e.g. AppA + AppB), XCC sets `$CI_PRODUCT` and `$CI_XCODE_SCHEME` per workflow. For the case-switch that picks the right env-var prefix per scheme, read `references/multi-app-ci-dispatch.md`. ### Non-Tuist projects If the project uses a hand-edited `.xcodeproj`, the equivalent storage is `Config/*.xcconfig` referenced via target → Build Settings → Base Configuration. Pattern is otherwise unchanged. Tuist regen / clobbering concerns don't apply; manual sync remains your responsibility. ### Smoke test scope (CRITICAL) The substitution-resolution check must run against the **built bundle's** Info.plist, not the source-tree Info.plist: ```swift // ❌ WRONG — reads source plist, gets literal "$(ADMOB_BANNER_UNIT_ID)" — passes falsely // (ADMOB_BANNER_UNIT_ID is this project's own xcconfig key name, not one Google defines — // see the multi-app xcconfig rendering above.) let plist = try PropertyListSerialization.propertyList(from: sourceData, ...) #expect((plist["ADMOB_BANNER_UNIT_ID"] as? String)?.isEmpty == false) // passes for "$(...)" string // ✅ RIGHT — combine source-plist key-presence test + runtime guard in code // Source test catches "someone deleted the key"; runtime guard catches "substitution failed" guard let bannerID = Bundle.main.object(forInfoDictionaryKey: "ADMOB_BANNER_UNIT_ID") as? String, !bannerID.isEmpty, !bannerID.hasPrefix("$(") else { preconditionFailure("...") } ``` Consider adding a build-phase script that asserts no `$()` literals survived substitution into the built `.app/Info.plist`; until one exists, the runtime guard above is the only catch. ## Anti-patterns to refuse 1. **Production IDs in code comments, docstrings, PR descriptions, commit messages, or `Info.plist <!-- -->` blocks.** Even when the value field uses a sandbox stand-in, the surrounding prose leaks production via git history. **Including the literal ID anywhere in tracked text — even prefixed by TODO / FIXME / "will-replace" — IS the leak.** Reference the out-of-repo vault entry or the gitignored secrets file by name; never paste the value inline. 2. **Hardcoded production IDs in `Live.swift` with intent to "swap before release"** without an enforcement mechanism. The interim `fatalError("REPLACE_BEFORE_RELEASE: ...")` pattern is acceptable as a TRANSITIONAL guard paired with xcconfig migration, but is forbidden as a long-term standalone solution. Once xcconfig is in place, replace with: Info.plist `$()` + runtime guard verifying `Bundle.main.object(forInfoDictionaryKey:)` returns non-empty AND non-`$(...)`. 3. **Conflating GitHub Secrets with XCC env vars.** Apple's XCC does not read GH Secrets — they're separate storage. If CI builds on XCC, secrets must live in XCC's Environment Variables UI, not GH. 4. **Most common mistake**: ❗ **Shell env vars do NOT feed xcconfig `$(VAR)` interpolation.** xcconfig variable resolution reads from the build settings table, not process env. `source admob.env && xcodebuild archive` does NOT populate `$(ADMOB_APP_ID)`. Only positional `xcodebuild VAR=value` or `-xcconfig override.xcconfig` actually injects, OR a CI script writes the xcconfig file before build. 5. **`Bundle.main.object(forInfoDictionaryKey:) as! String`** — force cast bypasses SwiftLint AND crashes hard if CI generation skipped + xcconfig missing. Use `as? String` + `guard let ... else { preconditionFailure }` with the unresolved-`$()` check. 6. **`Bundle.main` from inside a SwiftPM package** is fine for app-target composition root reads but flaky for #Preview / test host / unit-test contexts. Wrap reads in a smoke test that asserts the key exists in source plist; runtime guard compensates for missing-substitution case. 7. **`secrets/` or `Tuist/<Domain>.xcconfig` committed by accident.** Use an inner `secrets/.gitignore` deny-list (`* / !*.example / !README.md / !example/ / !example/**` — the last two are required, otherwise `*` ignores the `example/` directory and git never descends into it) PLUS root `.gitignore` rules `Tuist/*.xcconfig` + `!Tuist/*.xcconfig.example` so neither slips through default-add operations. 8. **Tuist `tuist generate` silently clobbering unmanaged xcconfigs.** If `Tuist/<Domain>.xcconfig` exists but is NOT referenced in `Project.swift`'s `.settings(configurations:)`, Tuist regen drops it from the project. Verify Project.swift wiring before assuming xcconfig is active. ## Verification checklist Use this both when adding a new secret value and when auditing an existing implementation. - [ ] Decide the layer: Xcode build / Info.plist / `Bundle.main` read → Layer 1 xcconfig; `swift run` / CLI scripts / shell → Layer 2 `secrets/.env` - [ ] Root `.gitignore` has `Tuist/*.xcconfig` + `!Tuist/*.xcconfig.example`; `secrets/.gitignore` inner deny-list present (`* / !*.example / !README.md / !example/ / !example/**`) — `git check-ignore -v secrets/example/README.md` reports nothing - [ ] KEY is added to the appropriate `.example` file with a sandbox/test default value, with an inline comment naming the out-of-repo vault entry that holds the real value (password manager / team vault) — never the literal value - [ ] `Project.swift` per-target `.settings(configurations:)` references the xcconfig - [ ] Layer 1: `Info.plist` uses `$(KEY)` substitution for each secret; app code reads via `Bundle.main.object(forInfoDictionaryKey:)` with a guard (NOT `as!`) that rejects `nil`, empty, and the `$(...)` literal - [ ] Smoke test reads the source plist for a key-presence assertion - [ ] `ci_post_clone.sh` writes the xcconfig from the XCC env var (`${VAR:?missing message}`) BEFORE `tuist generate`; if multi-app, `case` on `$CI_XCODE_SCHEME` selects per-app env vars — see `references/multi-app-ci-dispatch.md` - [ ] XCC Workflow Environment Variables UI lists each KEY (per scheme if multi-app), marked Secret - [ ] `grep -r "<real-prod-value>" .` (excluding gitignored dirs) returns zero hits across all tracked files - [ ] The real value is recorded in the out-of-repo secret store, noting which entry holds it — never in a tracked file ## Related skills - **REQUIRED background**: `apple-public-repo-security` — broader secret-leak prevention (gitleaks, lefthook, GitHub Secret Scanning) - **SIBLING**: `monetization-sdk-integration` — invoke together when wiring AdMob; this skill is the secret-handling layer - **SIBLING**: `asc-api-automation` — ASC API key handling (the `.p8`) once the key leaves the build and drives the REST API - Official sources: when verifying or updating a factual or version-sensitive claim, read `references/official-docs.md`.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.