Alt-Text 4 All is an HTTPS-first API that scrapes website images and generates AI-powered alt text to improve accessibility workflows.
The service exposes these primary capabilities:
- discover image URLs on a target page
- generate alt text for a specific image with provider-backed models such as
replicate,azure,ollama,huggingface,openai,openrouter, andtogether - generate alt text for all images on a page while preserving duplicate entries
- Website image scraping with relative URL resolution
- AI-generated descriptions for image URLs
- Async job handoff for slow single-image and page-description providers such as Replicate-backed
replicate - HTTPS-first local runtime with automatic HTTP -> HTTPS redirect
- Swagger UI for interactive API exploration
- Lint and test automation in CI
- Deterministic Postman/Newman API contract harness with local fixtures
- Optional token-based API access control for cost-bearing endpoints
- CI validates Node 20, 22, and 24.
engines.nodeallows Node 20 through 24; use Node 20 locally for the least friction.- npm 10+
- At least one provider configuration:
REPLICATE_API_TOKENfor thereplicatemodel- or
ACV_API_ENDPOINTplusACV_SUBSCRIPTION_KEYfor theazuremodel - or
OLLAMA_MODEL/OLLAMA_BASE_URLfor theollamamodel - or
OPENAI_API_KEY,HF_API_KEY/HF_TOKEN,OPENROUTER_API_KEY, orTOGETHER_API_KEY
git clone https://github.com/jsugg/alt-text-generator.git
cd alt-text-generator
cp .env.example .env
# edit .env and configure at least one provider
npm install
npm run devLocal defaults:
- HTTPS listens on
https://localhost:8443 - HTTP listens on
http://localhost:8080and redirects to HTTPS - Development TLS uses
TLS_KEY/TLS_CERTif provided, then localcerts/localhost*.pemif present, and otherwise auto-generates a localhost self-signed certificate in-process
Smoke checks:
curl -sk https://localhost:8443/api/health
curl -sk https://localhost:8443/api-docs/Note: -k skips TLS certificate verification. It is used here because development HTTPS may be self-signed.
Do not use -k for production traffic.
The repository includes a deterministic Postman/Newman harness that boots the app locally, starts a fixture server, and validates the public HTTP contract from outside the process.
Commands:
npm run postman:smoke
npm run postman:full
npm run postman:pre-production-provider
npm run postman:live-provider -- --base-url https://wcag.qcraft.com.br
npm run postman:post-deploy -- --base-url https://wcag.qcraft.com.brNotes:
postman:smokeis the fast deterministic gate.postman:fullruns the full local provider-integration suite, including protected-endpoint auth coverage, deterministic asyncreplicatepage-job success/failure scenarios, mocked provider-validation coverage, and JSON/JUnit reports underreports/newman/.- CI also emits
reports/jest/junit.xmlfrom the canonical Node 20 Jest lane and publishes one combined GitHub test report that joins Jest and Newman results. postman:pre-production-providerboots the app locally and runs the low-cost real-provider validation set used immediately before promotion, currently Hugging Face, OpenAI, and Together when configured.postman:live-provideris the production description-service validation command for deployed-app plus live-provider checks against a supplied base URL.postman:post-deployruns post-deploy smoke plus the same low-cost real-provider validation set against a supplied base URL.- Before Newman starts,
postman:live-providerandpostman:post-deploywait for consecutive stable health/auth probes so zero-downtime rollout overlap does not create false negatives. - When production auth is enabled,
postman:live-providerandpostman:post-deployreusePRODUCTION_DEPLOY_VALIDATION_API_TOKENfor provider-validation requests. - CI runs
postman:smokeon pull requests andpostman:fullonmain/productionpushes. - Post-deploy verification runs
postman:post-deployonproductionpushes so smoke and low-cost provider checks stay inside the Newman contract layer. - Post-deploy verification also reads
PRODUCTION_API_AUTH_ENABLEDandPRODUCTION_DEPLOY_VALIDATION_API_TOKENfrom the GitHub Actions environment so protected-endpoint checks match the deployed RenderAPI_AUTH_ENABLED/API_AUTH_TOKENSstate. - Provider-validation workflows use a single
LIVE_PROVIDER_SCOPEenum:auto,azure,replicate,huggingface,openai,openrouter,together, orall. LIVE_PROVIDER_SCOPE=autokeeps the provider-validation preference order: Azure, then Replicate, then Hugging Face, then OpenRouter, then OpenAI, then Together AI.provider_scope=allruns every provider that is configured for that environment; it does not require every supported provider to be enabled everywhere.- Local provider integration is fully mocked and never spends live provider credits.
- Pre-production and post-deploy use the low-cost Hugging Face, OpenAI, and Together subset. The async
replicatepage-job path is covered deterministically inpostman:full, and manual production live-provider validation can still run Replicate end to end against the same repo-controlled public provider-validation fixtures. postman:smokeandpostman:fulluse the local Postman environment;postman:live-providerandpostman:post-deployuse the live Postman environment.- Outside GitHub Actions, set
PROVIDER_VALIDATION_PUBLIC_REF=<pushed-sha-or-ref>if you need live-provider runs to use a branch-specific fixture revision before it lands onmain. - Production live-provider validation refuses localhost/private-network targets.
- Provider-validation runs upload Newman artifacts and append request, assertion, failure, and response-time metrics to the GitHub Actions step summary.
- Local harness runs accept self-signed development TLS.
- The deterministic full suite uses local stubs for Azure, Replicate, and OpenAI-compatible provider request shapes. The Replicate stub is stateful, so local replicate validation exercises the real
202 Acceptedpolling contract instead of an instant success shortcut.
Contribution standards for the contract suite live in docs/postman-standards.md.
Use these commands for local Allure generation:
npm run allure:clean
npm run test:allure
npm run postman:full:allure
npm run allure:generate
npm run allure:openOr run the combined local flow:
npm run report:allureNotes:
- Raw Allure files accumulate under
reports/allure-results/; generated HTML is written toreports/allure-report/. - The combined report merges one canonical Jest run with the Newman harness so local and CI results follow the same structure.
- CI uploads the generated HTML as the
allure-reportartifact. - Public Allure URLs:
https://jsugg.github.io/alt-text-generator/for the latestmainreport,https://jsugg.github.io/alt-text-generator/#suitesfor the suites view,https://jsugg.github.io/alt-text-generator/pr/for the pull-request report index, andhttps://jsugg.github.io/alt-text-generator/pr/<number>/for a specific same-repository pull request report. - Pushes to
mainpublish the latest generated report at the root URL, and same-repository pull requests publish their own report beneath/pr/<number>/. - CI now keeps separate Allure history streams for
main(ci-main) and same-repository pull requests (ci-pr-<number>), while deploy verification keeps its owndeploy-productionstream. - Same-repository PR reports restore and persist their own history stream, so each PR URL carries commit-to-commit trend data for that PR.
- CI prepares the composed Pages site, and the follow-up
Allure Pages Publishworkflow performs the actual GitHub Pages deployment so PR refs never have to deploy directly to the protectedgithub-pagesenvironment. Pushes tomainhand off to that workflow throughworkflow_run, while same-repository pull requests dispatch it explicitly with the source CIrun_idso PR report URLs publish reliably from the default-branch workflow code. The publish workflow still supports manualrun_idbackfills, still runs even when the source CI workflow failed after producing an Allure artifact, and still syncs each successful deployment back to thegh-pagesbranch so that branch remains the durable state source for future root/PR snapshot composition. - Fork PRs and post-deploy verification reports remain artifacts only; they do not publish to GitHub Pages.
- Fork pull requests and custom post-deploy verification URLs stay ephemeral and do not restore or persist history artifacts.
- Manual post-deploy verification against the canonical production URL only persists history when
persist_history=trueis selected in the workflow dispatch form. - CI only emits Jest Allure results from the Node 20 lane so unit tests do not appear three times in the merged report.
- The Allure CLI requires Java when you generate the HTML report locally or in CI.
Required at startup:
- At least one provider configuration:
REPLICATE_API_TOKENto registerreplicate- or
ACV_API_ENDPOINTplusACV_SUBSCRIPTION_KEYto registerazure - or
OLLAMA_MODEL/OLLAMA_BASE_URLto registerollama - or
OPENAI_API_KEY,HF_API_KEY/HF_TOKEN,OPENROUTER_API_KEY, orTOGETHER_API_KEY
Required for live Azure descriptions:
ACV_API_ENDPOINTACV_SUBSCRIPTION_KEY- The
azuremodel is only registered when the endpoint and subscription key are set together.
Common local settings:
API_AUTH_ENABLED- Optional boolean flag
- Defaults to
truewhenAPI_AUTH_TOKENScontains at least one token, otherwisefalse - When
true,API_AUTH_TOKENSmust contain at least one non-empty token
PORTandTLS_PORTPAGE_DESCRIPTION_CONCURRENCY- Optional positive integer
- Defaults to
3 - Caps concurrent provider calls per page-description request to reduce rate-limit and cost spikes
API_AUTH_TOKENS- Optional comma-separated tokens
- When API auth is enabled, scraper and description endpoints require
Authorization: Bearer <token>orX-API-Key: <token> ping,health, andapi-docsstay public
TRUST_PROXY_HOPS- Defaults to
1 - Controls how many proxy hops Express trusts for forwarded headers
- Defaults to
TLS_KEYandTLS_CERT- Optional in local development
- Required in production
- Can be file paths, inline PEM values, or base64-encoded PEM values
OUTBOUND_CA_BUNDLE_FILE- Optional app-managed supplemental PEM bundle for outbound HTTPS trust
- Use
npm run doctor:tls -- https://example.com --fix --write-env --env-file .env.testwhen a target works incurlbut fails in Node/app scraping
Advanced runtime settings such as worker count, scraper timeouts, rate limits, logging, Swagger URLs, and stubbed provider endpoints are documented in DEVELOPMENT.md.
Slow-provider job persistence can stay in memory or move to Redis through the DESCRIPTION_JOB_* settings documented there.
Slow-provider async jobs are currently driven by provider polling; there is no inbound webhook dependency in the runtime contract.
WORKER_COUNT=1 runs the app as a single process; cluster management is only enabled when WORKER_COUNT > 1.
When WORKER_COUNT > 1, configure a Redis-backed rate-limit store through RATE_LIMIT_STORE=redis (or auto). The default topology is external Redis via REDIS_URL/RATE_LIMIT_REDIS_URL, and a future-ready RATE_LIMIT_REDIS_TOPOLOGY=unit-local mode can default to redis://127.0.0.1:6379 for a pod-local/sidecar Redis design. Startup validation blocks clustered mode without Redis.
Clustered mode applies bounded restart backoff and a crash budget so persistent worker faults do not spin forever inside the app process.
Production logs stay on the process stream so platforms such as Render can collect them directly.
The Render deployment shape is versioned in render.yaml, while the Node runtime pin remains in package.json under engines.node.
/api/ping is the public liveness signal, while /api/health is a readiness endpoint: it returns 200 while the instance is ready and 503 while the process is draining during shutdown.
Error responses keep the existing top-level error message and add stable metadata:
error: human-readable messagecode: machine-readable error coderequestId: request correlation id when availabledetails: optional validation details for field-level failures
Interactive documentation: /api-docs
When API auth is enabled, use Swagger UI's Authorize flow with either a Bearer token or X-API-Key.
GET /api/scraper/images or /api/v1/scraper/images
- Summary: returns image URLs found on a website
- Query params:
url: URL-encoded address of the target website
Example:
curl -sk "https://localhost:8443/api/scraper/images?url=https%3A%2F%2Fdeveloper.chrome.com%2F"GET /api/accessibility/description or /api/v1/accessibility/description
- Summary: returns an alt-text description for a given image
- Query params:
image_source: URL-encoded address of the imagemodel: AI model identifier, such asreplicate,azure,ollama,huggingface,openai,openrouter, ortogether
- Notes:
- fast providers return
200with the description array immediately - slow async providers such as
replicatecan return202withjobId,status,pollAfterMs, andstatusUrl
- fast providers return
Example:
curl -sk "https://localhost:8443/api/accessibility/description?image_source=https%3A%2F%2Fwww.google.com%2Fimages%2Fbranding%2Fgooglelogo%2F1x%2Fgooglelogo_color_272x92dp.png&model=replicate"GET /api/accessibility/description-jobs/:jobId or /api/v1/accessibility/description-jobs/:jobId
- Summary: polls the current state of an async single-image description job
- Responses:
202while the provider is still processing200when the job has completed or failed
Example:
curl -sk "https://localhost:8443/api/accessibility/description-jobs/8dbd0c163f9c85166abac1d449f5fe3e78244da0edb8ff6ea7f2e4c4cc6db83d"GET /api/accessibility/descriptions or /api/v1/accessibility/descriptions
- Summary: scrapes a page and returns descriptions for its images
- Query params:
url: URL-encoded address of the target websitemodel: AI model identifier, such asreplicate,azure,ollama,huggingface,openai,openrouter, ortogether
- Notes:
- fast providers return
200with the completed page-description payload - slow async providers such as
replicatecan return202withjobId,status,pollAfterMs, andstatusUrl - preserves duplicate image entries in page order
- reuses one prediction per unique normalized image URL per request
- fast providers return
Example:
curl -sk "https://localhost:8443/api/accessibility/descriptions?url=https%3A%2F%2Fdeveloper.chrome.com%2F&model=replicate"GET /api/accessibility/page-description-jobs/:jobId or /api/v1/accessibility/page-description-jobs/:jobId
- Summary: polls the current state of an async page-description job
- Responses:
202while the provider is still processing200when the job has completed or failed
Example:
curl -sk "https://localhost:8443/api/accessibility/page-description-jobs/51cc340310a52659fbe9f3d2b9ef754f17f4f2376eb138dfb2cd7f0142ae5db0"Use the development guide for:
- complete environment-variable reference
- Postman/Newman harness usage and report interpretation
- GitHub Actions workflow and promotion guidance
- security and operational validation workflow guidance
- TLS and outbound CA troubleshooting
- lint, test, and live validation commands
- repo-controlled production description-service validation against real providers
See DEVELOPMENT.md.
This project is licensed under the MIT License. See the LICENSE file for details.
