Tarkman developers · v1

Public liturgy API

Build readers and discovery tools with ordered, multilingual liturgical text. Anonymous access. Read-only by design. No account or API key required.

Only published, active content

Every request is restricted to active liturgies explicitly marked public. Signing in, sending a JWT, or owning a liturgy never expands this API's visibility. There are no writes, account data, session controls, or private liturgies here.

Unpublishing, archiving, or deleting a liturgy prevents retrieval on the next request. It cannot revoke copies already downloaded by third parties or permanently offline devices. Do not publish material that must remain confidential.

Five-minute quick start

Published uses must prominently credit Tarkman: Source: Tarkman (tarkman.app). Place the credit where the content is displayed; see the terms of use for placement requirements and other conditions.

Use https://www.tarkman.app as the base URL. These commands select an actual catalog ID rather than assuming an example is published. The shell examples use curl and jq; an empty catalog is a valid result.

1. Browse and search

BASE=https://www.tarkman.app
curl --fail-with-body "$BASE/api/v1/liturgies?limit=10"
curl --fail-with-body --get "$BASE/api/v1/liturgies/search" \
  --data-urlencode 'q=prayer' --data-urlencode 'language=en' \
  --data-urlencode 'limit=10'

2. Inspect metadata and download ordered content

ID=$(curl --fail-with-body "$BASE/api/v1/liturgies?limit=1" | jq -r '.liturgies[0].id // empty')
if [ -n "$ID" ]; then
  curl --fail-with-body "$BASE/api/v1/liturgies/$ID"
  curl --fail-with-body -D content.headers \
    "$BASE/api/v1/liturgies/$ID/content" -o content.json
  ETAG=$(jq -r '.revision | "\"" + . + "\""' content.json)
  curl -i -H "If-None-Match: $ETAG" "$BASE/api/v1/liturgies/$ID/content"
fi

The last request can return 304 with no body. Reuse content.json only while it is a complete, validated copy for that revision. A changed response is 200; an unavailable item is 404, even with an old ETag.

3. Follow every page in JavaScript

Run in a browser console or an ES module. This example filters for readable English; use the other language codes below to select another language.

const base = "https://www.tarkman.app";
let after = null;
let firstId = null;
do {
  const query = new URLSearchParams({ limit: "10", language: "en" });
  if (after !== null) query.set("after_id", String(after));
  const response = await fetch(`${base}/api/v1/liturgies?${query}`, {
    credentials: "omit"
  });
  if (!response.ok) throw new Error(`Catalog: ${response.status}`);
  const page = await response.json();
  firstId ??= page.liturgies[0]?.id ?? null;
  for (const liturgy of page.liturgies) console.log(liturgy.name);
  after = page.next_after_id;
} while (after !== null); // Continue even when page.liturgies is empty.

if (firstId !== null) {
  const url = `${base}/api/v1/liturgies/${firstId}/content`;
  const response = await fetch(url, { credentials: "omit" });
  if (!response.ok) throw new Error(`Content: ${response.status}`);
  let savedBundle = await response.json(); // Validate before persisting.
  const etag = response.headers.get("ETag");
  if (etag) {
    const check = await fetch(url, {
      credentials: "omit", headers: { "If-None-Match": etag }
    });
    if (check.status === 200) savedBundle = await check.json();
    else if (check.status !== 304) throw new Error(`Revalidation: ${check.status}`);
  }
}

Endpoints

Canonical URLs are suffix-free. JSON responses use application/json; charset=utf-8. Only GET and HEAD are supported; no request in this namespace creates or changes content.

GET pathResult
/api/v1/liturgiesBounded catalog with current metadata and revisions
/api/v1/liturgies/searchCatalog matching name or description
/api/v1/liturgies/:idMetadata and available-language summary
/api/v1/liturgies/:id/contentComplete, ordered, session-independent text bundle
/api/v1/openapi.yamlPublic-only OpenAPI schemas and response contracts (YAML)

The linked OpenAPI document is the machine-readable reference, including exact required fields and nullability. It does not expose admin, account, editor, or live-session endpoints. The existing /public/liturgies.json and download URLs remain supported for existing clients; they are not replaced by this versioned API.

Catalog example

All IDs, names, counts and hashes below are synthetic illustrations, not production records.

{
  "schema_version": 1,
  "liturgies": [{
    "id": 701,
    "name": "Example morning prayer",
    "description": null,
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "languages_available": ["en", "grabar"],
    "occurrence_count": 1,
    "download_bytes": 735,
    "recommended_rank": null
  }],
  "next_after_id": null
}

Detail returns {schema_version, liturgy: {id, name, description}, revision, languages_available, occurrence_count, download_bytes}. Content returns {schema_version, liturgy, languages_available, parts, revision}. Catalog download_bytes is the complete compact UTF-8 content response size before compression, not an estimate of readable text.

Queries, languages and pagination

ParameterContract
limitCatalog/search: decimal integer 1–100, default 50.
after_idCatalog/search: positive decimal cursor, at most 9007199254740991. Omit for the first page.
languageCatalog/search: one supported language code. Requires at least one readable eligible occurrence, not complete translation coverage.
recommendedCatalog only: true or false. Only true restricts to configured verified recommendations; no recommendations may be configured.
qSearch only, required: trimmed Unicode string of 2–100 characters, matching liturgy name and description only.

Unknown parameters are rejected with 400. Decimal IDs/cursors reject signs, whitespace, leading zeroes, fractions and values beyond JavaScript's safe integer range. There is no offset, page number, arbitrary sort or part/explanation full-text search. Search treats % and _ as literal characters, not wildcards; matching is case-insensitive using PostgreSQL's database locale. Accent equivalence is not promised; accented and unaccented strings are not automatically folded together.

  • en — English
  • grabar — Classical Armenian (Grabar)
  • translit — transliteration
  • ru — Russian
  • hy_east — Eastern Armenian
  • hy_west — Western Armenian

Catalog and search sort by ID ascending, never recommendation rank or relevance. Send the returned next_after_id unchanged until it is null. Each page is a separate snapshot, so concurrent publication changes may affect later pages.

A page examines at most limit candidates. Invalid or oversized candidates may be omitted; a source-work budget can end a page early. The cursor advances over omitted candidates, so even an empty array may have a continuation. Empty results are 200, not an error. Absence from a page is never authoritative revocation of a saved bundle.

The content model

parts contains ordered occurrences, not a deduplicated collection of Parts. Use liturgy_part_id as the occurrence identity: the same part_id may appear more than once. Ordering is by positive position, then occurrence ID.

{
  "liturgy_part_id": 801,
  "liturgy_id": 701,
  "part_id": 901,
  "position": 1,
  "role": "priest",
  "posture": "stand",
  "page": null,
  "text": {
    "en": "Example prayer text.",
    "grabar": null,
    "translit": null,
    "ru": null,
    "hy_east": null,
    "hy_west": null
  },
  "requires_session_context": false,
  "explanations": []
}

The occurrence above is an independent synthetic example. Roles are priest, deacon, choir, people, instruction, title, leader, all, clergy. Posture is stand, sit, kneel or null. Page is a positive integer only when exactly one associated page belongs to this liturgy; ambiguous or missing associations yield null.

All six text keys are present; missing or blank translations are null. languages_available means at least one readable occurrence, not that every part is translated. Grabar is distinct from both modern Armenian language fields. For session-variable Parts, English is null and requires_session_context is true; other static translations remain available. No session is created and no live interpolation is performed.

Only published explanations attached to that exact occurrence are included, ordered by row order then ID (null row orders last), as {id, liturgy_part_id, title, description}. Drafts, archived explanations, owner/updater IDs, metadata, account fields, assignments, citations, attachments, signed URLs, media downloads, timing and live-session state are excluded.

Text is sanitized to remove HTML elements; Markdown remains authored text. Render as plain text (for example with textContent), or use a restricted Markdown renderer with raw HTML, remote images and automatic resource fetching disabled. Never insert API text with innerHTML.

Revisions, ETags and consistency

Each revision is an opaque SHA-256 digest of the exact allowed content. Child text, occurrence ordering and published explanation changes update the revision even without a parent timestamp change. Excluded private metadata does not.

JSON responses use Cache-Control: private, no-cache, including errors and 304s. Content responses expose a quoted ETag; send it unchanged in If-None-Match. Weak validators, lists of validators and * are supported, but 304 is returned only after rechecking current publication eligibility and rebuilding the current content. Do not use shared/CDN body caches or skip revalidation.

Each response is one read-only database snapshot. Changes committed after that snapshot affect the next request. Catalog and content requests may legitimately see different revisions: replace a saved bundle atomically from one complete content response, never mix their parts.

Complete bundles are limited to 10 MiB (10,485,760 bytes); content is never truncated to produce a success. Bounded source reads and a ten-second database statement timeout protect the service. A 422 or transient failure should not discard a previously valid saved copy.

Errors, rate limits and retries

Typed errors use {"schema_version":1,"error":{"code":"…"}}; do not depend on HTML error pages or exception messages.

StatusMeaning / action
200Complete response, including an empty catalog.
304No body. Reuse only the corresponding complete saved bundle.
400invalid_public_liturgy_parameters: correct the query before retrying.
404public_liturgy_unavailable: missing, malformed, private, archived and deleted IDs are indistinguishable. Only this recognized per-item error is authoritative unavailability.
404unsupported_api_version: use a supported version; do not fall back silently to an internal API.
422invalid_public_liturgy_content or bundle_too_large: report the content problem; retain a previously valid copy.
429rate_limited: wait at least the integer Retry-After seconds before retrying.
5xxTransient server failure: retry with bounded exponential backoff and jitter; retain valid saved copies. A timeout or malformed response is not revocation.

Initial per-IP limits are shared across the versioned API and legacy public library: 60 requests per minute overall, 6 catalog/search requests per minute, and 20 combined detail/content/download reads per minute. Catalog and search rebuild multiple bundles, so have a lower budget. Each category also counts against the overall limit; switching routes does not reset the allowance. Twenty item reads permit seven initial downloads plus seven conditional checks in one window; shared client networks share their allowance. These are adjustable operational defaults, not measured throughput guarantees or an SLA. Avoid concurrent bulk downloads and tight retry loops. Unsupported methods cannot mutate data and return a non-disclosing error.

Browser access and privacy

Public v1 permits cross-origin GET and HEAD, with narrowly scoped OPTIONS preflight. Send no cookies or credentials (credentials: "omit"). Browsers may send Accept, Content-Type and If-None-Match; ETag and Retry-After are exposed to browser code. This does not grant access to internal APIs.

Queries should contain public catalog terms only, never personal information, passwords or tokens. See our privacy policy. Your application is responsible for safe display and storage of downloaded content and for its own privacy disclosures.

Versioning and support

Use the explicit /api/v1 prefix. Unsupported versions return a JSON 404 with unsupported_api_version, never an automatic redirect to another API. Only explicitly safe optional additions may be made within v1; breaking changes require a new version such as /api/v2. No v2 release is scheduled.

We intend to give notice of major changes where practical, but we don't guarantee it. The API may change, be limited, suspended or ended, and content may be removed at any time; see the terms of use. Keep existing legacy public-library integrations on their documented contract unless intentionally adopting the versioned API.

Questions or a content problem? Contact Tarkman support with the endpoint, status and a synthetic reproduction. Do not send private content or authentication tokens. Download the public OpenAPI YAML for the exact schema.

Terms of use

These terms apply when you use the Tarkman public API or content obtained from it. By using either, you agree to them. If you don't agree, don't use the API.

What you may do

You may use, display, store, and redistribute content from the API in your websites, apps, and other works, including commercial ones, as long as you follow these terms.

Citing Tarkman

Anything you publish that uses this content must prominently credit Tarkman as a source, with a link to https://www.tarkman.app where links are possible:

Source: Tarkman (tarkman.app)

The credit must be easy for your users to see without searching for it. Place it where the content is displayed. If that isn't practical, place it on your app or site's opening screen and in its description or store listing. A credit that appears only in a licenses page, settings page, or source code isn't prominent enough. In print, place it on the cover or title page.

If you change the text, say so. Don't imply that Tarkman wrote, reviewed, or endorses your version or your product.

Other people's rights

Tarkman doesn't claim to own the liturgical texts of the Church, and some content comes from other sources. These terms give you permission only to the extent we are able to give it. You are responsible for deciding whether your use requires anyone else's permission.

No guarantees

The API and its content are provided "as is" and "as available", without warranties of any kind, including availability, accuracy, completeness, or fitness for a particular purpose. Don't rely on the API or its content where an error or outage would cause harm.

We may change, limit, suspend, or end the API or remove content at any time, for any reason. We intend to give notice of major changes where practical, but we don't guarantee it. We may block any use that overloads the service or breaks these terms.

Limit of liability

To the fullest extent the law allows, Tarkman and its operators are not liable for any damages or losses arising from your use of, or inability to use, the API or its content, including losses caused by outages, errors, changes, or the end of the service.

Changes to these terms

We may update these terms by posting a new version on this page. Continuing to use the API means you accept the updated terms.

Last updated: 3 November 2026