Extended metadata for a Zeens photo post — EXIF, categories, license, and optional external link. The rkey matches the rkey of the associated app.bsky.feed.post record.
tid
Timestamp-based ID
Properties
aiDeclaration
string
Optional
Creator's declaration about AI involvement in this image.
no-ai, ai-assisted, contains-aicategories
array
of
string
Optional
Photography categories selected by the creator: up to 3 per photo in the post, so 12 for a four-photo post.
maxLength: 12 itemscolorHexes
array
of
string
Optional
Deprecated — use `palette` instead. Was intended to hold the colours behind the hue tags in `colors`, positionally aligned, but was never written by any client: every record in the wild lacks it. `palette` supersedes it and is not tag-aligned.
maxLength: 3 itemscolors
array
of
string
Optional
Color tags detected on the cover image at upload: up to 3 prominent hues (red/orange/yellow/green/teal/blue/purple/pink), ranked most prominent first and de-duplicated so adjacent hues do not repeat, followed by up to 2 tone tags (warm/cool/earth/pastel). Empty when monochrome. A dark or muted photo still carries its hues, and its temperature. Used by user-built feeds and search to filter posts by color.
maxLength: 5 itemscreatedAt
string
datetime
Required
Timestamp when this metadata record was created.
crossLink
boolean
Optional
True if the creator enabled 'Cross-link to Bluesky' at upload. When set, a link to the canonical Zeens post URL was appended to the app.bsky.feed.post text, and the Zeens post page shows a 'View on Bluesky' pill (and hides the appended self-link in the rendered caption).
editCount
integer
Optional
Number of times the creator has edited this post via Zeens. Editing is capped (currently 3 edits) to discourage rewriting published photos; the edit button disables once this reaches the cap.
minimum: 0exif
ref
#exifData
Optional
Camera EXIF metadata extracted from the original image.
film
ref
#filmData
Optional
Film/analog metadata. Present when the post is marked with app.zeens.film on the underlying app.bsky.feed.post record. EXIF and film blocks can coexist.
hasBorder
boolean
Optional
Deprecated — use images[].borderCrop instead.
images
array
of
ref
#imageMeta
Optional
Per-image extended metadata, indexed by position in the post's app.bsky.embed.images.images array.
maxLength: 4 itemslicense
string
Optional
Creative Commons or rights license for the photo.
all-rights-reserved, cc-by, cc-by-sa, cc-by-nc, cc-by-nc-sa, cc-by-nd, cc-by-nc-nd, cc0link
ref
#externalLink
Optional
Optional external link attached by the creator (e.g. portfolio, print shop).
location
string
Optional
Optional free-form location name where the photo was taken.
maxLength: 256 byteslocationLat
integer
Optional
Latitude in WGS84, scaled by 1e7 (AT Protocol does not support floats). Divide by 1e7 to get decimal degrees.
minimum: -900000000maximum: 900000000locationLng
integer
Optional
Longitude in WGS84, scaled by 1e7 (AT Protocol does not support floats). Divide by 1e7 to get decimal degrees.
minimum: -1800000000maximum: 1800000000monochrome
boolean
Optional
True if the cover image is black-and-white / grayscale. Detected client-side at upload by averaging HSV saturation across a downscaled bitmap (threshold 0.05). Powers the Black & White feed.
noAiTraining
boolean
Optional
Deprecated — use aiDeclaration instead. True if creator chose 'no-ai'.
palette
array
of
string
Optional
The cover image's actual colours, as #rrggbb, most prominent first. Up to eight: the largest distinct colours, plus accents — small but vivid regions a viewer notices first, which selection by area alone never surfaces. Displayed as unnamed swatches; `colors` remains what search filters on. Empty when monochrome. Absent on records written before palette extraction, which fall back to the hue buckets' stand-in colours.
maxLength: 8 itemspaletteShares
array
of
integer
Optional
How much of the cover image each `palette` colour covers, in basis points (1/100 of a percent), positionally aligned with `palette`. Integers because AT Protocol rejects floats; basis points rather than percent so a small accent does not round to 0. Lets a renderer size swatches by prominence. Absent on records written before it existed, which render every swatch at equal weight.
maxLength: 8 itemspost
string
at-uri
Required
AT-URI of the app.bsky.feed.post record this metadata belongs to.
View raw schema
{
"description": "Extended metadata for a Zeens photo post — EXIF, categories, license, and optional external link. The rkey matches the rkey of the associated app.bsky.feed.post record.",
"key": "tid",
"record": {
"properties": {
"aiDeclaration": {
"description": "Creator's declaration about AI involvement in this image.",
"knownValues": [
"no-ai",
"ai-assisted",
"contains-ai"
],
"type": "string"
},
"categories": {
"description": "Photography categories selected by the creator: up to 3 per photo in the post, so 12 for a four-photo post.",
"items": {
"maxLength": 64,
"type": "string"
},
"maxLength": 12,
"type": "array"
},
"colorHexes": {
"description": "Deprecated — use `palette` instead. Was intended to hold the colours behind the hue tags in `colors`, positionally aligned, but was never written by any client: every record in the wild lacks it. `palette` supersedes it and is not tag-aligned.",
"items": {
"maxLength": 7,
"type": "string"
},
"maxLength": 3,
"type": "array"
},
"colors": {
"description": "Color tags detected on the cover image at upload: up to 3 prominent hues (red/orange/yellow/green/teal/blue/purple/pink), ranked most prominent first and de-duplicated so adjacent hues do not repeat, followed by up to 2 tone tags (warm/cool/earth/pastel). Empty when monochrome. A dark or muted photo still carries its hues, and its temperature. Used by user-built feeds and search to filter posts by color.",
"items": {
"maxLength": 32,
"type": "string"
},
"maxLength": 5,
"type": "array"
},
"createdAt": {
"description": "Timestamp when this metadata record was created.",
"format": "datetime",
"type": "string"
},
"crossLink": {
"description": "True if the creator enabled 'Cross-link to Bluesky' at upload. When set, a link to the canonical Zeens post URL was appended to the app.bsky.feed.post text, and the Zeens post page shows a 'View on Bluesky' pill (and hides the appended self-link in the rendered caption).",
"type": "boolean"
},
"editCount": {
"description": "Number of times the creator has edited this post via Zeens. Editing is capped (currently 3 edits) to discourage rewriting published photos; the edit button disables once this reaches the cap.",
"minimum": 0,
"type": "integer"
},
"exif": {
"description": "Camera EXIF metadata extracted from the original image.",
"ref": "#exifData",
"type": "ref"
},
"film": {
"description": "Film/analog metadata. Present when the post is marked with app.zeens.film on the underlying app.bsky.feed.post record. EXIF and film blocks can coexist.",
"ref": "#filmData",
"type": "ref"
},
"hasBorder": {
"description": "Deprecated — use images[].borderCrop instead.",
"type": "boolean"
},
"images": {
"description": "Per-image extended metadata, indexed by position in the post's app.bsky.embed.images.images array.",
"items": {
"ref": "#imageMeta",
"type": "ref"
},
"maxLength": 4,
"type": "array"
},
"license": {
"description": "Creative Commons or rights license for the photo.",
"knownValues": [
"all-rights-reserved",
"cc-by",
"cc-by-sa",
"cc-by-nc",
"cc-by-nc-sa",
"cc-by-nd",
"cc-by-nc-nd",
"cc0"
],
"type": "string"
},
"link": {
"description": "Optional external link attached by the creator (e.g. portfolio, print shop).",
"ref": "#externalLink",
"type": "ref"
},
"location": {
"description": "Optional free-form location name where the photo was taken.",
"maxLength": 256,
"type": "string"
},
"locationLat": {
"description": "Latitude in WGS84, scaled by 1e7 (AT Protocol does not support floats). Divide by 1e7 to get decimal degrees.",
"maximum": 900000000,
"minimum": -900000000,
"type": "integer"
},
"locationLng": {
"description": "Longitude in WGS84, scaled by 1e7 (AT Protocol does not support floats). Divide by 1e7 to get decimal degrees.",
"maximum": 1800000000,
"minimum": -1800000000,
"type": "integer"
},
"monochrome": {
"description": "True if the cover image is black-and-white / grayscale. Detected client-side at upload by averaging HSV saturation across a downscaled bitmap (threshold 0.05). Powers the Black & White feed.",
"type": "boolean"
},
"noAiTraining": {
"description": "Deprecated — use aiDeclaration instead. True if creator chose 'no-ai'.",
"type": "boolean"
},
"palette": {
"description": "The cover image's actual colours, as #rrggbb, most prominent first. Up to eight: the largest distinct colours, plus accents — small but vivid regions a viewer notices first, which selection by area alone never surfaces. Displayed as unnamed swatches; `colors` remains what search filters on. Empty when monochrome. Absent on records written before palette extraction, which fall back to the hue buckets' stand-in colours.",
"items": {
"maxLength": 7,
"type": "string"
},
"maxLength": 8,
"type": "array"
},
"paletteShares": {
"description": "How much of the cover image each `palette` colour covers, in basis points (1/100 of a percent), positionally aligned with `palette`. Integers because AT Protocol rejects floats; basis points rather than percent so a small accent does not round to 0. Lets a renderer size swatches by prominence. Absent on records written before it existed, which render every swatch at equal weight.",
"items": {
"maximum": 10000,
"minimum": 0,
"type": "integer"
},
"maxLength": 8,
"type": "array"
},
"post": {
"description": "AT-URI of the app.bsky.feed.post record this metadata belongs to.",
"format": "at-uri",
"type": "string"
}
},
"required": [
"post",
"createdAt"
],
"type": "object"
},
"type": "record"
}