Qafiyah is an open-source reference for Arabic poetry. Search every verse for any word or phrase, or browse by poet, meter, rhyme, era, and theme, at qafiyah.com. The code, the data, and a free JSON API are all public, and contributions are welcome.
| Poems | Verses | Poets | Meters | Rhymes | Eras | Themes | Collections |
|---|---|---|---|---|---|---|---|
| 374,176 | 6,287,322 | 17,338 | 44 | 36 | 12 | 10 | 1 |
From snapshot 0031_23_09_2026. Poets, eras, and meters each count one "unknown" entry for unattributed records.
Read. Browse qafiyah.com, or follow @qafiyahx on X for a poem a day.
Ask the API. No key, no sign-up:
curl "https://api.qafiyah.com/v1/poems/random?option=lines"Run it locally. You need Bun 1.3.14, a Docker engine (OrbStack or Docker Desktop), and rustup:
git clone https://github.com/raaqimorg/qafiyah.git
cd qafiyah
bun install
bun run devThe first run starts Postgres and Elasticsearch in Docker, restores a 100-poem sample, builds the search index, and serves the site at http://localhost:4321 and the API at http://localhost:8787, with no .env or secrets. Everyday commands and troubleshooting are in docs/development.md.
https://api.qafiyah.com/v1 serves read-only JSON for poems, poets, eras, meters, rhymes, themes, collections, and full-text search. Explore the interactive docs, the OpenAPI document, or llms.txt.
| Access | Limit |
|---|---|
| No key | 60 requests per hour per address (an IPv6 /64 is one address) |
| Free key | 500 requests per hour, burst of 10 per second |
| Higher | write to api@qafiyah.com |
Sign in with Google or GitHub on the developers page to create a key, and send it as the x-api-key header. Responses report x-ratelimit-remaining, a refused request returns 429 with Retry-After, and errors are RFC 9457 problem+json.
Postgres is the source of truth, Elasticsearch is a search index rebuilt from it, and poet avatars are served from Cloudflare R2 at cdn.qafiyah.com. Snapshots are versioned in this repo: database dumps in data/db/ and avatar archives in data/avatars/.
The 100-poem sample that bun run dev restores is plain text. The full snapshots are encrypted but free for anyone: email dumps@qafiyah.com or avatars@qafiyah.com with what you plan to build, and a passphrase comes right back, with no vetting. Encryption only keeps a record withdrawable later, since a plaintext file pushed to a public repo stays in every clone. Details are in data/README.md.
A request enters through Cloudflare, passes the web application firewall, and reaches the web app or the API. The web app renders pages on the server by calling the API, which reads Postgres for records and Elasticsearch for search.
flowchart LR
browser["Browser"] --> cf["Cloudflare edge + Tunnel"]
cf --> edge["edge-gateway<br/>nginx + ModSecurity CRS"]
edge -->|"qafiyah.com<br/>api.qafiyah.com"| web["web<br/>nginx + Astro SSR + React islands"]
web -->|"api.qafiyah.com<br/>+ SSR /v1 fetch"| api["api<br/>Rust, axum"]
api --> db[("Postgres")]
api --> es[("Elasticsearch")]
indexer["search-indexer<br/>one-shot job"] --> db
indexer --> es
browser -->|"t.qafiyah.com"| telemetry["telemetry-proxy<br/>Cloudflare Worker"]
telemetry --> sentry[("Sentry")]
| Part | Role | Built with |
|---|---|---|
apps/web |
Server-rendered pages, with React islands for search, the random poem, and settings | Astro, React, Tailwind CSS, Bun, PostHog |
apps/api |
Read-only /v1 API, its OpenAPI contract generated from the code |
Rust, axum, sqlx, utoipa, PostgreSQL 18 |
apps/search-indexer |
One-shot job that builds a fresh index from Postgres and swaps the alias | Rust, Elasticsearch 9 |
crates/elasticsearch |
Index schema, Arabic analyzers, and client shared by the two above | Rust |
apps/edge-gateway |
Web application firewall in front of everything, configuration only | nginx, OWASP ModSecurity CRS |
apps/telemetry-proxy |
Forwards browser error reports to Sentry from a first-party hostname | Cloudflare Workers |
apps/inspector |
Dev-only report of the metadata on every page type | TypeScript |
scripts/ |
Repo tooling and the CI gate, one bun run name per entry point |
Bun, Turborepo, oxlint, oxfmt, vitest |
Search reads Arabic the way people do: hamza forms, alif maqsura, and ta marbuta fold to their base letters, diacritics are ignored for matching and kept for display, and an exact title always outranks scattered matches. The full account is in docs/search.md.
Production is one VPS running six containers behind a Cloudflare Tunnel, with nothing reachable inbound, and deploys are a manual step. docs/topology.md maps the whole system and docs/deployment/ covers operations.
Report bugs and ideas in GitHub issues. To change code, open an issue first, then fork the repo, branch off main, run bun run ci, and open a pull request linked to the issue; CONTRIBUTING.md walks through it. Good places to start are the web app, search relevance, and the repo tooling. Every component has an AGENTS.md describing its shape, and docs/exceptions.md lists where the code departs from the usual approach, and why.
What bun run ci checks
scripts/ci.ts runs lint and format, type and repo checks, unit tests, and contract snapshots (OpenAPI, the generated client, Elasticsearch queries), then, with Docker, database-backed tests and smoke tests against the built stack. The pre-commit hook checks only the staged files, the pre-push hook runs the gate without Docker, and GitHub Actions runs the gate plus gitleaks on every push and pull request, and each Docker phase when the change touches what it uses (docs/topology.md, "CI/CD topology"). Clippy denies unwrap, expect, panic, indexing, and lossy casts in production code. More in docs/testing.md.
Documentation map
docs/development.md: running the stack locally, everyday commands, worktrees, committing.docs/topology.md: a diagram-first map of the whole system, code and production.docs/deployment/README.md: the production entry point.docs/domain.md: what a poem, poet, meter, rhyme, era, theme, and collection mean.docs/search.md: Arabic text handling, relevance tiers, snippet selection.docs/code-conventions.mdand the TypeScript, Rust, testing, and pull-request files next to it: how code, tests, and commits are written.docs/identity.md: canonical name, description, organization, and links.AGENTS.mdand each component'sAGENTS.md: repo layout and component shapes, written for AI agents and useful to anyone.data/README.md: the encrypted database and avatar snapshots.
Qafiyah is maintained by Raaqim, an open-source organization behind several Arabic-language projects, together with its contributors. The site's about page tells the story. To reach us, write to the address that fits below; API and data requests have their own addresses in the sections above.
| For | Write to |
|---|---|
| General contact | mail@qafiyah.com |
| Problems with the site or the data | issues@qafiyah.com |
| Security reports, kept private (policy) | security@qafiyah.com |
| Conduct concerns (code of conduct) | conduct@qafiyah.com |
We are also on Telegram.
The code and documentation are released under the MIT license. The data, meaning the poem catalog the site and API serve and the snapshots in data/, is dedicated to the public domain under CC0 1.0. The site's typeface, Amiri, is used under the SIL Open Font License 1.1, the code of conduct is adapted from the Contributor Covenant 2.1, and the edge gateway runs the stock owasp/modsecurity-crs nginx image.
الشِّعرُ ديوانُ العرب
ابن عباس

