Equalify Iris
https://github.com/EqualifyEverything/equalify-iris.git
Image-to-Accessible-HTML parsing service. Iris converts a sequential set of image files (e.g. the rendered pages of a PDF) into a single content-only, WCAG 2.2 AA accessible HTML document, using specialized per-content-type agents, a self-extending builder, and an iterative reader/copy-editor review loop.
## Sustainability
> Equalify Iris is Open Source. Sustainability is key to sustaining its growth. With that
in mind, we hope you use and alter the codebase.
> Iris is built by Equalify Inc (https://equalify.app/). Continued
support and development are paid for when you hire us to host or support any instance. Please
consider hiring us.
The pipeline runs in five phases (PRD ยง6):
max_review_iterations (default 3).
When Iris meets content a specialist agent would handle better than the general pass, it drafts
that agent and automatically files a labeled iris-agent-suggestion GitHub issue (with the
agent code + context) on the upstream repo, attributed to the logged-in user. Maintainers
triage those issues; merged agents become part of the shared agents/ library. (This replaces
the PRD's fork+PR-on-close flow โ see Implementation notes.)
Requires Node.js 24+ (the service runs TypeScript directly via Node's built-in type
stripping and uses the built-in node:sqlite), and a git checkout of the agent library
(this repo's agents/ directory works). For PDF uploads, install poppler-utils
(pdftoppm/pdfinfo) โ brew install poppler on macOS, apt-get install poppler-utils on
Debian/Ubuntu. (The Docker image includes it.)
git clone https://github.com/EqualifyEverything/equalify-iris
cd equalify-iris
npm install
cp .env.example .env # fill in GitHub OAuth + a model provider key
cp config.example.yaml config.yaml
# load env and run
set -a; source .env; set +a
npm start # -> http://localhost:8080
Or with Docker (multi-arch; Mac Mini / Linux ARM are first-class targets):
cp .env.example .env # fill in values
docker compose up
Check it's alive:
curl http://localhost:8080/v1/health
Or just open the accessible browser app at the root for a no-API walkthrough (sign in with GitHub โ upload page images โ convert โ view the accessible HTML):
http://localhost:8080/
Deployment is configured in config.yaml (PRD ยง10.3). ${ENV_VAR} references are expanded
from the environment at startup; changes require a restart.
agents/ is a gitgit pull from upstream.
vision,structured_output, text); the deployment maps capabilities to a provider + concrete
model. v1 ships OpenRouter and Amazon Bedrock adapters. Adding a provider is a small
adapter implementing the ModelProvider interface in src/providers/types.ts. Models are
set per provider (default_model + per_capability), and can be overridden per agent via
providers.per_agent โ either a string (provider only) or { provider, model }. Resolution
falls back: per-agent model โ provider per_capability โ provider default_model.
gh CLI uses). Set github.client_id only to point at your
own OAuth App; client_secret is needed only if you enable the web redirect flow.
All endpoints are under /v1 and (except auth and health) require
Authorization: Bearer <github_token>.
| Method & path | Purpose |
|---|---|
GET /v1/health | Liveness probe |
GET /v1/auth/github/start | Begin OAuth (web clients) |
GET /v1/auth/github/callback | OAuth callback โ returns access token |
POST /v1/auth/github/device | Begin device flow (CLI clients) |
POST /v1/auth/github/device/poll | Poll device flow (send { "device_code": ... }) |
GET /v1/me | Current GitHub user + config |
GET /v1/sessions | List the caller's sessions |
POST /v1/sessions | Create a session, upload images and/or PDFs (multipart/form-data) |
GET /v1/sessions/{id} | Poll status |
GET /v1/sessions/{id}/output | Fetch the HTML when ready |
POST /v1/sessions/{id}/feedback | Submit feedback, trigger a re-run |
POST /v1/sessions/{id}/close | Finalize the session and clean tmp |
GET /v1/sessions/{id}/logs | Fetch the run log (ndjson) |
GET /v1/sessions/{id}/diagnostics | Timing/health summary (phase + per-call durations, in-flight/hung call) |
./test/e2e.sh.
Example โ create a session (order of images parts is the processing order, ยง9.2):
curl -X POST http://localhost:8080/v1/sessions \
-H "Authorization: Bearer $TOKEN" \
-F "images=@page-001.png" \
-F "images=@page-002.png" \
-F 'config={"max_review_iterations": 3}'
Then poll GET /v1/sessions/{id} until status is ready_for_review, fetch
GET /v1/sessions/{id}/output, and POST /v1/sessions/{id}/close to finalize.
agents/ # the agent library (git checkout; v1 content agents)
src/
config.ts # config loader (${ENV} expansion)
providers/ # ModelProvider interface + openrouter & bedrock adapters
agents/loader.ts # loads agent .md files, pins git SHA (ยง7.3)
pipeline/ # triage, extraction, builder, reconciliation, assembly, review
auth/ # GitHub OAuth + device flow + bearer middleware
github/ # auto-files labeled agent-suggestion issues
store/ # node:sqlite metadata store + on-disk session layout (ยง8.1)
routes/ # /v1 endpoints
index.ts # server entry point
data/ # sessions/, tmp/, and the SQLite DB (created at runtime)
A few places where the PRD left a decision open, and where v1 intentionally stops:
runs/<run-id> vs sessions/<session-id>. The PRD references both (ยง7.3/ยง7.5 vs ยง8.1).new-agents.md,
etc. under sessions/<session-id>/, matching the authoritative layout in ยง8.1.
color-contrast rule is disabled โ it cannot be assessed without rendering and is out of
scope.
feedback_rerun event) and theoutput.html is snapshotted to sessions/<id>/history/ so it can be reverted to. A
revert endpoint is out of v1 API scope (not in ยง9); the data is preserved to enable it.
iris-agent-suggestion) with the agent code + context. It uses a deployment **service
token** (IRIS_GITHUB_TOKEN) โ never end users' identities โ and is a no-op when unset.
Simpler to triage, and nothing is published under a user's name without consent.
Intentionally not built in v1 (the PRD frames each as optional / alternative / out of scope):
PostgreSQL and S3 backends (ยง10.2 โ "supported alternative," SQLite + local FS is the v1
reference), the per-user config endpoint (ยง9.1 โ "not specified in v1"), and webhooks (ยง9.4 โ
out of scope). The only endpoint beyond the PRD is GET /v1/health, a standard liveness probe.
See CONTRIBUTING.md and our Code of Conduct. Found an accessibility barrier โ in the app or in the HTML it produces? Please open an Accessibility issue; those are our top priority.
GNU AGPL-3.0-or-later. Iris is copyleft: if you modify it and run it as a network service, you must make your modified source available to its users (AGPL ยง13). The hosted and self-hosted versions are functionally identical โ see the Sustainability notice above, and please consider hiring Equalify to host or support your instance.