A published version is a package: one manifest.json and the PNG images it names. Check every file before you use it. Because a published version never changes, a package that passes once can be kept for as long as you like.
manifest.json
version
manifestUrl
manifestSha256
minEngineVersion
assets
sha256
bytes
An entry in assets looks like this:
{ "assets": { "crown_3f2a91bc.png": { "sha256": "3f2a91bc…", "bytes": 20711 } } }
So this image is at https://…/lenses/8f3c2a10-…/3/crown_3f2a91bc.png, next to https://…/lenses/8f3c2a10-…/3/manifest.json.
https://…/lenses/8f3c2a10-…/3/crown_3f2a91bc.png
https://…/lenses/8f3c2a10-…/3/manifest.json
The manifest and its images are plain downloads. Don't send your API key with them. They don't count as delivery calls.
From a shell:
curl -s -o manifest.json "$MANIFEST_URL" sha256sum manifest.json # or: shasum -a 256 manifest.json # The hex digest must equal manifestSha256.
In Swift, with CryptoKit:
import CryptoKit import Foundation enum DownloadError: Error { case hashMismatch } func sha256Hex(_ data: Data) -> String { SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined() } let (bytes, _) = try await URLSession.shared.data(from: manifestURL) guard sha256Hex(bytes) == expectedSha256 else { throw DownloadError.hashMismatch }
In Kotlin:
import java.security.MessageDigest fun sha256Hex(bytes: ByteArray): String = MessageDigest.getInstance("SHA-256").digest(bytes) .joinToString("") { "%02x".format(it) } check(sha256Hex(bytes) == expectedSha256) { "Hash mismatch" }
New fields can appear in delivery answers at any time. Ignore the fields you don't know, so a new one never breaks your app. Removing or changing a field would be announced first.
Swift's Codable ignores unknown keys by default:
Codable
// The "data" object of a face lens answer. struct LensVersion: Decodable { let lensId: String let version: Int let manifestUrl: URL let manifestSha256: String let totalBytes: Int let minEngineVersion: Int let publishedAt: String let previewUrl: URL? }
With kotlinx.serialization, set ignoreUnknownKeys = true:
kotlinx.serialization
ignoreUnknownKeys = true
val json = Json { ignoreUnknownKeys = true } // The "data" object of a face lens answer. @Serializable data class LensVersion( val lensId: String, val version: Int, val manifestUrl: String, val manifestSha256: String, val totalBytes: Long, val minEngineVersion: Int, val publishedAt: String, val previewUrl: String? = null, )
Leniency is for the API's answers only. The manifest itself is strict and versioned. Its formatVersion changes only with a breaking change, and its minEngineVersion says which renderer can draw it. Check both before you read the rest, and treat a manifest your renderer doesn't understand as one to skip, not to guess at.
formatVersion