# town.walls.photo

> Published by [walls.town](https://lexicon.garden/identity/did:plc:geruez2v64zoge3l4sztt4kz)

✓ This is the authoritative definition for this NSID.

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:geruez2v64zoge3l4sztt4kz/town.walls.photo)
- [Documentation](https://lexicon.garden/lexicon/did:plc:geruez2v64zoge3l4sztt4kz/town.walls.photo/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:geruez2v64zoge3l4sztt4kz/town.walls.photo/examples)

## Definitions

### `town.walls.photo`

**Type**: `record`

A documented photo of a graffiti piece.

**Key**: `tid`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `tags` | `array` | No | Faceted tags. Conventions (parsed/enforced in app code, not the lexicon): kind:<piece\|throwup\|tag\|mural\|sticker\|roller\|wheatpaste\|other> (exactly one expected), crew:<name>, surface:<wall\|truck\|pole\|shutter\|...>. Plus freeform tags (e.g. "trackside", "two-color"). |
| `image` | `blob` | No | The displayed photo. Optional in schema; exactly one of image or grainPost is required (enforced in app code). |
| `notes` | `string` | No |  |
| `camera` | `ref` → `#camera` | No |  |
| `artists` | `array` | No | Artist names. Free strings; a piece may have several. |
| `caption` | `string` | No |  |
| `bskyPost` | `ref` → `com.atproto.repo.strongRef` | No | strongRef to an app.bsky.feed.post cross-posting this sighting. |
| `location` | `ref` → `#location` | No |  |
| `longform` | `ref` → `com.atproto.repo.strongRef` | No | strongRef to a site.standard.document for pieces with an essay. Reserved; not wired yet. |
| `createdAt` | `string` (datetime) | Yes | Record creation time. |
| `grainPost` | `ref` → `com.atproto.repo.strongRef` | No | strongRef to a grain post serving as the photo. Alternative to image (exactly one of image/grainPost required, app-enforced). |
| `capturedAt` | `string` (datetime) | No | When the photo was taken (EXIF DateTimeOriginal), if known. |
| `siteRecord` | `ref` → `com.atproto.repo.strongRef` | No | strongRef to the site.standard.* record the website publishes per photo for discoverability. |
| `aspectRatio` | `ref` → `#aspectRatio` | No | Pixel dimensions of the image, used for justified layout / to avoid reflow. Best-effort. |
| `grainCrosspost` | `ref` → `com.atproto.repo.strongRef` | No | strongRef to a social.grain.gallery this admin created to syndicate the sighting onto grain (gallery == grain post). Distinct from grainPost, which is an external grain post used AS the photo source. Set by the syndication step; absence means the grain leg hasn't run. |

### `town.walls.photo#camera`

**Type**: `object`

Best-effort capture metadata from EXIF or a grain record.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `iso` | `integer` | No |  |
| `lens` | `string` | No |  |
| `make` | `string` | No |  |
| `model` | `string` | No |  |
| `shutter` | `string` | No |  |
| `aperture` | `string` | No |  |
| `focalLength` | `string` | No |  |

### `town.walls.photo#location`

**Type**: `object`

Capture location. lat/lng are decimal-degree STRINGS: ATProto lexicons have no float type (only integer). NOTE: coordinates are stored at full precision here. The PUBLIC website coarsens them at render time via geo.coarsen, but the record itself is public via listRecords — exact coords are readable by anyone reading the repo directly.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `lat` | `string` | No | Decimal degrees, full precision. e.g. "37.77531". |
| `lng` | `string` | No | Decimal degrees, full precision. e.g. "-122.22417". |
| `name` | `string` | No | Optional human label, e.g. "E 12th St underpass". |

### `town.walls.photo#aspectRatio`

**Type**: `object`

Image pixel dimensions. width/height are integers (e.g. for a 6000x4000 photo).

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `width` | `integer` | Yes |  |
| `height` | `integer` | Yes |  |

## Raw Schema

```json
{
  "id": "town.walls.photo",
  "defs": {
    "main": {
      "key": "tid",
      "type": "record",
      "record": {
        "type": "object",
        "required": [
          "createdAt"
        ],
        "properties": {
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxLength": 40,
            "description": "Faceted tags. Conventions (parsed/enforced in app code, not the lexicon): kind:<piece|throwup|tag|mural|sticker|roller|wheatpaste|other> (exactly one expected), crew:<name>, surface:<wall|truck|pole|shutter|...>. Plus freeform tags (e.g. \"trackside\", \"two-color\")."
          },
          "image": {
            "type": "blob",
            "accept": [
              "image/*"
            ],
            "maxSize": 4000000,
            "description": "The displayed photo. Optional in schema; exactly one of image or grainPost is required (enforced in app code)."
          },
          "notes": {
            "type": "string",
            "maxLength": 10000,
            "maxGraphemes": 1000
          },
          "camera": {
            "ref": "#camera",
            "type": "ref"
          },
          "artists": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxLength": 20,
            "description": "Artist names. Free strings; a piece may have several."
          },
          "caption": {
            "type": "string",
            "maxLength": 3000,
            "maxGraphemes": 300
          },
          "bskyPost": {
            "ref": "com.atproto.repo.strongRef",
            "type": "ref",
            "description": "strongRef to an app.bsky.feed.post cross-posting this sighting."
          },
          "location": {
            "ref": "#location",
            "type": "ref"
          },
          "longform": {
            "ref": "com.atproto.repo.strongRef",
            "type": "ref",
            "description": "strongRef to a site.standard.document for pieces with an essay. Reserved; not wired yet."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime",
            "description": "Record creation time."
          },
          "grainPost": {
            "ref": "com.atproto.repo.strongRef",
            "type": "ref",
            "description": "strongRef to a grain post serving as the photo. Alternative to image (exactly one of image/grainPost required, app-enforced)."
          },
          "capturedAt": {
            "type": "string",
            "format": "datetime",
            "description": "When the photo was taken (EXIF DateTimeOriginal), if known."
          },
          "siteRecord": {
            "ref": "com.atproto.repo.strongRef",
            "type": "ref",
            "description": "strongRef to the site.standard.* record the website publishes per photo for discoverability."
          },
          "aspectRatio": {
            "ref": "#aspectRatio",
            "type": "ref",
            "description": "Pixel dimensions of the image, used for justified layout / to avoid reflow. Best-effort."
          },
          "grainCrosspost": {
            "ref": "com.atproto.repo.strongRef",
            "type": "ref",
            "description": "strongRef to a social.grain.gallery this admin created to syndicate the sighting onto grain (gallery == grain post). Distinct from grainPost, which is an external grain post used AS the photo source. Set by the syndication step; absence means the grain leg hasn't run."
          }
        }
      },
      "description": "A documented photo of a graffiti piece."
    },
    "camera": {
      "type": "object",
      "properties": {
        "iso": {
          "type": "integer"
        },
        "lens": {
          "type": "string"
        },
        "make": {
          "type": "string"
        },
        "model": {
          "type": "string"
        },
        "shutter": {
          "type": "string"
        },
        "aperture": {
          "type": "string"
        },
        "focalLength": {
          "type": "string"
        }
      },
      "description": "Best-effort capture metadata from EXIF or a grain record."
    },
    "location": {
      "type": "object",
      "properties": {
        "lat": {
          "type": "string",
          "description": "Decimal degrees, full precision. e.g. \"37.77531\"."
        },
        "lng": {
          "type": "string",
          "description": "Decimal degrees, full precision. e.g. \"-122.22417\"."
        },
        "name": {
          "type": "string",
          "description": "Optional human label, e.g. \"E 12th St underpass\"."
        }
      },
      "description": "Capture location. lat/lng are decimal-degree STRINGS: ATProto lexicons have no float type (only integer). NOTE: coordinates are stored at full precision here. The PUBLIC website coarsens them at render time via geo.coarsen, but the record itself is public via listRecords — exact coords are readable by anyone reading the repo directly."
    },
    "aspectRatio": {
      "type": "object",
      "required": [
        "width",
        "height"
      ],
      "properties": {
        "width": {
          "type": "integer",
          "minimum": 1
        },
        "height": {
          "type": "integer",
          "minimum": 1
        }
      },
      "description": "Image pixel dimensions. width/height are integers (e.g. for a 6000x4000 photo)."
    }
  },
  "$type": "com.atproto.lexicon.schema",
  "lexicon": 1
}
```
