# Buddhist Calendar contribution file, format v1: field reference The file a monastery, lineage or Dharma centre already listed in the Buddhist Calendar (https://www.firstbuddha.space) sends to publish its events, the anniversaries of its masters and daily words. `schema.json` is the machine-readable version of this page; `example.json` is a complete valid file. The requirements in six languages are at https://www.firstbuddha.space/sangha/format, and the in-browser checker is at https://www.firstbuddha.space/sangha/check. ## The file - JSON, UTF-8, text in Unicode NFC, no comments, no trailing commas, at most 2 MiB per file. - Named `-.json`; parts `--part1.json` and so on. - **Unknown fields are errors** (most are typos such as `startdate`). The only extra field allowed is `$schema`. - **One file is the whole snapshot.** It replaces everything the community sent before: an entry left out is removed. An entry's `id` keeps it the same from one file to the next. - **Text is a map by language**: `{ "uk": "…", "en": "…" }`. Keys are lower-case ISO 639 codes (`^[a-z]{2,3}$`). Values are plain text: no HTML, no Markdown, no `<` followed by a letter. - Links go only in `url`, `videoUrl` and `community.website`, always `https://`. A web address or an email address or phone number inside a public text is reported as a warning (`text.link`, `text.contact`). ## Top level | Field | Required | Rule | |---|---|---| | `$schema` | no | `"https://www.firstbuddha.space/sangha/contribute/schema.v1.json"` | | `format` | yes | exactly `"firstbuddha-sangha-contribution"` | | `version` | yes | `1` | | `source` | yes | the community's id as the editor gave it, `^[a-z][a-z0-9-]{1,31}$`, e.g. `"knsl"` | | `community` | no | `{ "name": ≤120, "website": https }`, for the editor; `name` required inside it | | `contact` | yes | `{ "name": ≤120, "email": ≤254, "role": ≤80 }`; `name` and `email` required. Used only to reply; **never published**; removed on import | | `confirm` | yes | `{ "authorised": true, "rights": true }`, both literally `true`. `authorised`: "I am authorised to speak for this community". `rights`: "we have the right to publish these texts and pictures" | | `languages` | yes | 1–8 codes, no repeats: the languages **the community itself wrote**; the first is the main one | | `machineTranslated` | no | codes of languages that exist only as a machine translation the community asked for; none of them may be in `languages`. Published only if the editor approves them | | `zone` | when any event has times | IANA time zone of the events' times: `"Europe/Kyiv"`, `"Asia/Kathmandu"`, `"America/New_York"` | | `part` | no | `{ "n": 1, "of": 3 }`, one of several files of one submission (at most 20) | | `entries` | yes | 1 or more entries (below) | `contact.email` must look like an address (`^[^\s@]+@[^\s@]+\.[^\s@]+$`). ## Every entry | Field | Required | Rule | |---|---|---| | `type` | yes | `"event"`, `"anniversary"` or `"note"` | | `id` | yes | `^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$`: lower-case Latin letters, digits, hyphens, 1–64, no hyphen at either end; unique in the whole submission (across types and parts); the same every time the file is sent | | `lang` | no | the language the entry was first written in; default: the first of `languages`; must be one of `languages` | | `url` | no | https, ≤2048 | | `image` | no | one picture, see "Pictures" | Every text-map key must be in `languages` or `machineTranslated`. A required text map (an event's or anniversary's `title`, a note's `text`) must contain the entry's own language. Suggested ids: events `2026-tara-retreat`; anniversaries `founder-parinirvana` (no year: they recur); dated notes `2026-11-01-quote` (`2026-11-01-quote-2` for a second); yearly notes `full-moon-reminder`. ## `"type": "event"` A dated event: a teaching, puja, drubchen, retreat, course, class, gathering. | Field | Required | Rule | |---|---|---| | `kind` | yes | `teaching` (teachings, empowerments, transmissions), `ritual` (pujas, drubchens, practice retreats), `monastic` (monastic observances, ordinations), `course` (courses, seminars, study programmes), `class` (regular classes), `community` (community days, service, gatherings), `other` | | `title` | yes | text ≤160; the event's own name | | `subtitle` | no | text ≤200; every subtitle language needs a title in that language | | `summary` | no | text ≤400; what the phone app shows | | `description` | no | text ≤3000; kept in the feed, not shown by the app | | `location` | no | text ≤200: a hall, a town, "Online" | | `zone` | no | IANA zone, only when this event's times are in a zone other than the file's `zone` | | `allDay` | yes | `true` or `false` | | `startDate` | yes | `YYYY-MM-DD` | | `endDate` | no | `YYYY-MM-DD`, inclusive, not before `startDate`; default `startDate` | | `startTime` | when `allDay` is `false` | `HH:MM`, 24-hour; not allowed when `allDay` is `true` | | `endTime` | no | `HH:MM`, 24-hour; not allowed when `allDay` is `true`; on a single date, after `startTime` | | `daily` | recommended | only for a timed event over several dates: `true` = one session each date from `startTime` to `endTime` (`endTime` required and later than `startTime`); `false` = one continuous span from the first date's start to the last date's end. **Left out, it means one span** (the checker warns `time.dailyMissing`), so always write it for such an event | | `videoUrl` | no | https: a live stream or recording | | `registration` | no | `"required"` or `"none"` | | `status` | no | only `"cancelled"`; keep a cancelled event in the file with the same id | The calendar computes everything else (exact start and end instants, the link to a festival on the same day). Up to 300 events per submission; recurring classes are listed date by date (or the community gives the editor an iCal address to follow instead). ## `"type": "anniversary"` The anniversary of a master or a lineage day, **defined once by Tibetan date and recurring every year**. There is never a western date and never a year: the calendar computes the western date for every year. | Field | Required | Rule | |---|---|---| | `title` | yes | text ≤160 | | `subtitle` | no | text ≤200; needs a title in the same language | | `summary` | no | text ≤400; the community's own words only | | `tibetan` | yes | a Tibetan date rule, see below | Plus the common `id`, `lang`, `url`, `image`. Up to 100 anniversaries. A title may join a list's heading and an item ("Parinirvana of" + the name as written); a lifespan given in the material goes into `subtitle` exactly as written ("1904-1987"). Nothing the material lacks: no year, no lifespan, no epithet, no biography. ## `"type": "note"` Daily words: a quote, an instruction or a reminder, once on a date or every year on a Tibetan date. | Field | Required | Rule | |---|---|---| | `kind` | yes | `quote` (someone's words), `instruction` (how to practise), `reminder` (for the community) | | `title` | no | text ≤120 | | `text` | yes | text ≤600, exactly as published; single line breaks are kept (verse), a blank line separates paragraphs | | `attribution` | for a `quote` | `{ "author": text ≤120, "work": text ≤200, "ref": string ≤80, "translator": text ≤120 }`, all optional except that a `quote` needs `author`; `ref` (chapter, verse or page) is a plain string, not a text map. `author` and `translator` hold the name in its base form, as printed under the quote | | `date` | one of `date` and `tibetan` | `YYYY-MM-DD`: shown once, on that date | | `tibetan` | one of `date` and `tibetan` | a Tibetan date rule: shown every year on that day | Plus the common `id`, `lang`, `url`, `image`. Up to 800 notes; **at most 3 notes on any one date** (the checker counts dated notes; the calendar applies the same cap after placing the yearly ones and keeps the first three in file order). Each note appears on its own date (the calendar's feed carries the notes from a week back to two months ahead), so a whole year of daily words can be sent at once. ## Tibetan date rule (`tibetan`) ``` "tibetan": { "month": 3, "day": 10 } "tibetan": { "month": 2, "day": 10, "leapMonth": "both" } ``` | Field | Required | Rule | |---|---|---| | `month` | yes | integer 1–12, the Tibetan lunar month | | `day` | yes | integer 1–30, the lunar day | | `leapMonth` | no | `"first"` (default), `"second"` or `"both"`: which half of the month to use in a year when that month is doubled; ignored in other years | - The reckoning is **Phugpa (Men-Tsee-Khang)**, the calendar's own. A **skipped** day is observed on the next day; of a **doubled** day the **first** is kept. - A community that follows another almanac (Tsurphu, for one) publishes those days as dated events, with western dates from its own almanac. - Nobody but the calendar converts: not the community, not an AI helper. Never put a western date or a year into a Tibetan date rule. - To find the lunar month and day of a known western date, the community looks in its almanac, or opens `https://www.firstbuddha.space/calendar/YYYY-MM-DD` (the day page shows the lunar day and month). An AI helper may point there but never reads it off for them. ## Pictures (`image`) At most one per entry; at most 60 different pictures per submission. ```json { "file": "tara-retreat.jpg", "credit": "Photo: Example Dharma Centre", "rightsConfirmed": true, "alt": { "en": "Statue of Green Tara on the shrine" } } ``` | Field | Required | Rule | |---|---|---| | `file` | `file` or `url` | the attached file's exact name: `^[A-Za-z0-9][A-Za-z0-9._-]{0,100}\.(jpe?g\|png\|webp\|heic)$`, any case; no spaces, no folders | | `url` | `file` or `url` | a picture the calendar already publishes for this community: `https://www.firstbuddha.space/media/sangha//<16 hex digits>.jpg` | | `credit` | yes | ≤120, plain string: who took it ("Photo: Ani Pema / KNSL") | | `rightsConfirmed` | yes | literally `true`: the community took the photo or has written permission to publish it | | `alt` | recommended | text ≤200 per language: what the picture shows, for readers who cannot see it (a warning when missing) | Never both `file` and `url`. The file itself: JPEG, PNG, WebP or HEIC, up to 10 MB, at least 600 px on the long edge (1200 or more recommended). The calendar removes all hidden data (location, camera), turns it upright, resizes it to 1280 px at most and publishes it as JPEG with the credit. ## Common cases - **A day kept every year by the western calendar** (a founding day): not an anniversary. Publish this year's date as a dated event (or a dated note). - **Days the calendar already shows**: Losar, the four Düchen, Guru Rinpoche days, full and new moons. Not anniversaries, even when the community calls one a lineage day (Guru Rinpoche's birthday, say): publish what the community does on those days as dated events or yearly notes. Anniversaries are for the community's own masters and lineage days. - **A practice kept every month on a lunar day** (a full-moon gathering, a tsok every 10th day): either one dated event per date, with the dates from the community's almanac, or twelve yearly notes (months 1-12, the same day), each with `"leapMonth": "both"`. Without `both`, a doubled month gets only its first half's day. - **Different times on different days** (Saturday 10:00-18:00, Sunday 10:00-14:00): one entry per day (`2026-12-seminar-1`, `…-2`, the same title), or one span from the first start to the last end (`"daily": false`). Never one time for all the days. - **A name in another grammatical case** («переклад Ірини Бойко»): `author` and `translator` take the base form; ask how the name is printed. - **A yearly day known only by one year's western date**: the community finds its Tibetan month and day in its almanac or on the calendar's day page (above). An AI helper never looks it up or works it out for them. - **A month given by name** (Saga Dawa, Chotrul): write its number, as the community confirms it. - **A one-off event known only by Tibetan dates** (a drubchen "from the 1st to the 8th day of the 12th month"): an event needs its western dates from the community's almanac. Until the community has them, leave it out. - **A year missing or a vague date** ("next Sunday"): ask; never guess. - **A recurring class**: one event per date, with the exact dates (at most 300 events); or the community's iCal address, which the editor can follow instead. - **Prices, fees, donations, sponsorship**: not in the file. They stay on the community's own page, which `url` links to. - **A cancelled event**: keep it, with the same `id` and `"status": "cancelled"`, so that followers see the cancellation. - **An event already over**: leave it out. - **Two languages in one text** ("Ретрит / Retreat"): give each language its own key. ## Limits | What | Limit | |---|---| | Entries per submission | 1000 in total: events 300, anniversaries 100, notes 800 | | Notes on one date | 3 | | Pictures | 60 different files; each ≤10 MB, long edge ≥600 px | | Parts | 20; each file ≤2 MiB | | Languages | 8 | | `title` / `subtitle` / `summary` / `description` / `location` | 160 / 200 / 400 / 3000 / 200 | | Note `title` / `text` | 120 / 600 | | `author` / `work` / `ref` / `translator` | 120 / 200 / 80 / 120 | | `credit` / `alt` | 120 / 200 | | `community.name` / `contact.name` / `contact.role` / `contact.email` | 120 / 120 / 80 / 254 | | `url`, `videoUrl`, `website` | https, ≤2048 | Lengths are counted in characters, after trimming. ## Parts Only when a file would be too long to write in one go: - every part is a complete, valid file; - all parts carry the same `source`, `contact`, `confirm`, `languages`, `machineTranslated` and `zone`, and each its own share of the entries; - `part` is `{ "n": 1, "of": 3 }`, `{ "n": 2, "of": 3 }`, …; all of them are sent together, none missing; - ids are unique across all parts; the snapshot is all parts together. ## Updating what is published The community's current file is at `https://www.firstbuddha.space/api/sangha/v1//contribution.json`. It is the form kept by the calendar, so before sending it again: 1. delete `importedAt`; 2. inside every `image`, delete `width` and `height` (keep `url`, `credit`, `rightsConfirmed`, `alt`); 3. add `contact` again (and `$schema` if you like); 4. change, add or remove entries; keep every existing `id`. It already has `lang` on every entry and `zone` and `endDate` on every event; those may stay. Events long over and machine translations the editor did not keep are no longer in it. ## Sending Check the file at https://www.firstbuddha.space/sangha/check (it never leaves the browser). Then email the file (all parts) and the pictures to **burners.reissue3b@icloud.com** (the address is also on https://www.firstbuddha.space/sangha/format), subject `Sangha file: `. Pictures that do not fit in one email go in several emails with the same subject. The editor reads every file; what is approved appears with the next site update; what is not approved is never published. The contact details are used only to reply and are removed before publishing; everything else becomes public. ## A complete small file A fictional community (`"source": "example"`); `example.json` is a longer version of it. Copy the shape, never the content. ```json { "$schema": "https://www.firstbuddha.space/sangha/contribute/schema.v1.json", "format": "firstbuddha-sangha-contribution", "version": 1, "source": "example", "community": { "name": "Example Dharma Centre", "website": "https://example.org" }, "contact": { "name": "Name Surname", "email": "office@example.org", "role": "Secretary" }, "confirm": { "authorised": true, "rights": true }, "languages": ["en", "uk"], "zone": "Europe/Kyiv", "entries": [ { "type": "event", "id": "2026-tara-retreat", "kind": "ritual", "title": { "en": "Green Tara retreat", "uk": "Ретрит Зеленої Тари" }, "allDay": false, "startDate": "2026-12-04", "endDate": "2026-12-06", "startTime": "09:00", "endTime": "17:00", "daily": true, "url": "https://example.org/tara-2026", "image": { "file": "tara-retreat.jpg", "credit": "Photo: Example Dharma Centre", "rightsConfirmed": true, "alt": { "en": "Statue of Green Tara on the shrine" } } }, { "type": "anniversary", "id": "founding-teacher-parinirvana", "title": { "en": "Parinirvana of our founding teacher" }, "tibetan": { "month": 3, "day": 10 } }, { "type": "note", "id": "2026-12-01-quote", "kind": "quote", "date": "2026-12-01", "text": { "en": "The words of the quote, exactly as they are published." }, "attribution": { "author": { "en": "Name of the teacher" }, "work": { "en": "Title of the book" }, "ref": "p. 12" } } ] } ``` ## What the checker reports Each problem has a code, a path (`entries[3].title.uk`; with several files `files[1].entries[3].title.uk`) and, inside an entry, that entry's id. **(w)** marks a warning, which does not stop the file from being sent; everything else is an error. A JSON Schema validator reports a wrong field inside an entry as one `oneOf` error on the whole entry; the checker names the field itself. | Group | Codes | |---|---| | JSON and file | `json.syntax` (not valid JSON), `json.size` (over 2 MiB), `file.object` (not a JSON object), `file.format`, `file.version` | | Fields | `field.unknown` (a field the format does not have; often a typo), `field.missing`, `field.type` (wrong kind of value) | | Source and contact | `source.pattern`, `source.unknown` (w: an id the calendar does not list yet), `contact.email`, `confirm.false` | | Languages and zone | `languages.code`, `languages.empty`, `languages.duplicate`, `mt.overlap` (a code in both `languages` and `machineTranslated`), `mt.present` (w), `zone.invalid` | | Entries and ids | `entries.empty`, `entries.limit`, `id.pattern`, `id.duplicate`, `type.invalid`, `lang.undeclared` (a language not in `languages` or `machineTranslated`) | | Text | `text.empty`, `text.length`, `text.markup`, `text.ownLanguage` (the entry's own language is missing), `text.subtitleWithoutTitle`, `text.placeholder` (a `REPLACE:` text or a sample value from the template: `your-community-id`, `you@example.org`, `https://example.org/your-event-page`, `your-picture.jpg`), `text.contact` (w), `text.link` (w) | | Kinds | `kind.event`, `kind.note` | | Dates and times | `date.format`, `date.order` (end before start), `date.past` (w), `date.far` (w: more than 400 days ahead), `time.format`, `time.allDay` (times on an all-day event), `time.start` (a timed event without `startTime`), `time.daily` (`daily` on one date or an all-day event), `time.dailyOrder`, `time.dailyMissing` (w: a timed event over several dates without `daily`, read as one span), `time.order` (ends before it starts) | | Tibetan dates | `tibetan.month`, `tibetan.day`, `tibetan.leapMonth`, `timing.both` (a note with both `date` and `tibetan`), `timing.none` (with neither) | | Other fields | `url.https`, `registration.value`, `status.value` | | Notes | `attribution.author` (a quote without an author), `notes.perDay` (more than 3 notes on a date) | | Pictures | `image.file`, `image.url`, `image.both`, `image.rights`, `image.credit`, `image.alt` (w: no description), `images.limit` | | Parts | `parts.mixed` (parts that differ in their header, or a part without `part`), `parts.missing`, `parts.duplicate` | | Backstop | `event.rule` (a rule of the calendar's feed; the detail says which) |