Skip to content
PramodTKodagPublic

About

Sarcasm as a Service. an AI-powered humor API that generates a unique, witty response on every request. Roasts, honest commit messages, ELI5, and 25+ more endpoints.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

5 stars

Watchers

1 watching

Forks

Repository files navigation

snark logo

Snark

Sarcasm as a Service — an open-source, self-hostable AI humor API that generates a unique, witty response on every request.

CI License: AGPL v3 Python 3.12+ Django 5.1

Snark API returning live AI-generated humor


What is Snark?

Snark is an open-source, self-hostable REST API that serves AI-generated humor — roasts, excuses, corporate jargon, fortune cookies, and 30+ other endpoints. Deploy it in one command with Docker, or use the free public API. Every response is uniquely generated by an LLM. No two responses are ever the same.

  • Zero authentication — fully public, no API keys needed
  • Multi-provider — Groq (free), Gemini, Claude, or any OpenAI-compatible endpoint (including a local Ollama), chosen in one config file, with automatic fallback
  • Rate limited — 50 requests/hour per IP
  • One-command stack — docker compose up brings the API, PostgreSQL, and Redis together; nothing external to provision
  • Observability-ready — structured JSON logs to any aggregator, plus an opt-in Loki + Alloy + Grafana + Prometheus stack (--profile observability) with a token-guarded /metrics endpoint
  • Self-hosted — run the whole thing on your own infrastructure

Quick Start

Prerequisites

  • Docker & Docker Compose — the only requirement; PostgreSQL and Redis are bundled into the stack
  • Python 3.12+ — optional, only for running tests or tooling outside Docker

1. Clone and configure

git clone https://github.com/PramodTKodag/snark.git
cd snark
cp .env.example .env

Edit .env and add your API key for at least one provider:

# Groq is free — get a key at https://console.groq.com/keys
GROQ_API_KEY=your-groq-api-key-here

2. Run with Docker

docker compose --profile dev up --build

The API will be available at http://localhost:8100.

3. Try it out

# Get a creative excuse to say no
curl http://localhost:8100/v1/wit/say-no/

# Get roasted
curl http://localhost:8100/v1/wit/roast/dave/

# Roast any public URL
curl "http://localhost:8100/v1/wit/roast-url/?url=https://example.com"

# Explain quantum physics like you're 5
curl "http://localhost:8100/v1/wit/explain-like-im-5/?q=quantum+physics"

# Generate an honest git commit message
curl http://localhost:8100/v1/wit/commit-message/

Example response

Every endpoint returns the same compact JSON shape:

// GET /v1/wit/proverb/
{
  "response": "He who checks phone during conversation is lost in a sea of distraction, says the Scroll of Digital Wisdom.",
  "persona": "The Ancient Sage",
  "cached": false
}

response is the generated text, persona is the voice that produced it, and cached tells you whether it came from the 5-minute response cache.

API Endpoints

All endpoints are GET requests under /v1/wit/. Most accept optional ?q=, ?mood=, ?length=, ?lang=, and ?stream= query parameters.

Core

Endpoint Description
/v1/wit/say-no/ Creative excuse to say no
/v1/wit/random-excuse/ Random excuse generator
/v1/wit/corporate-jargon/ Corporate buzzword generator
/v1/wit/commit-message/ Honest git commit message
/v1/wit/hot-take/ Spicy hot take
/v1/wit/compliment/ Backhanded compliment
/v1/wit/bug-blame/ Who to blame for the bug
/v1/wit/roast/<name>/ Personalized roast
/v1/wit/worth-it/?q= Decision oracle
/v1/wit/explain-like-im-5/?q= ELI5 any topic

Viral and Workplace

Endpoint Description
/v1/wit/pickup-line/ Tech pickup line
/v1/wit/social-bio/ Social media bio generator
/v1/wit/motivation/ Questionable motivation
/v1/wit/fortune-cookie/ Fortune cookie message
/v1/wit/name-suggestion/?q= Name suggestion for anything
/v1/wit/standup-update/ Standup update generator
/v1/wit/code-review/ Code review comment
/v1/wit/meeting-excuse/ Meeting escape excuse
/v1/wit/jargon-translator/?q= Translate jargon to plain English
/v1/wit/incident-postmortem/ Incident postmortem generator

More

Endpoint Description
/v1/wit/tech-battle/?q= Tech vs tech showdown
/v1/wit/rate-anything/?q= Rate anything out of 10
/v1/wit/horoscope/ Developer horoscope
/v1/wit/tldr/?q= TL;DR any topic
/v1/wit/interview-question/ Absurd interview question
/v1/wit/honest-changelog/ Honest changelog entry
/v1/wit/debug-story/ Debugging horror story
/v1/wit/proverb/ Tech proverb

Discovery and Meta

Endpoint Description
/v1/wit/personas/ List every available persona (slug, name, tone)
/v1/wit/moods/ List accepted mood values
/v1/wit/random/ Surprise me — random persona
/v1/wit/roast-github/<username>/ Roast a public GitHub profile
/v1/wit/roast-url/?url= Roast any public URL (SSRF-safe fetch + Open Graph)
/v1/wit/stats/ Usage stats (totals and top personas)

Mood Parameter

Add ?mood= to any endpoint to change the tone:

sarcastic, angry, funny, sad, excited, dramatic, passive-aggressive,
philosophical, wholesome, unhinged, dry, chaotic, chill, spicy, deadpan

Fetch this list programmatically from /v1/wit/moods/.

Example: curl "http://localhost:8100/v1/wit/motivation/?mood=passive-aggressive"

Length and Language

Add ?length= (short, medium, or long) to control response size, and ?lang= to get the response in another language:

curl "http://localhost:8100/v1/wit/hot-take/?q=pizza&lang=Spanish"
curl "http://localhost:8100/v1/wit/proverb/?q=coding&length=long"

Streaming

Add ?stream=true to any endpoint and use curl -N to receive the response token-by-token as Server-Sent Events, instead of one JSON object:

Snark streaming a response token by token, live from the LLM

curl -N "http://localhost:8100/v1/wit/hot-take/?q=tabs+vs+spaces&stream=true"

data: {"delta": "Forcing"}
data: {"delta": " everyone"}
data: {"delta": " to"}
...
data: {"persona": "The Hot Take Machine", "done": true}
data: [DONE]

Streaming bypasses the response cache, so streamed calls always hit the LLM. Omit stream for the default cached JSON response.

Batch

POST /v1/wit/batch/ runs up to 5 personas in a single request. Each item names a persona slug (see /v1/wit/personas/) plus the same optional q, mood, length, and lang fields. Results come back in order; a bad slug yields a per-item error without failing the rest.

curl -X POST "http://localhost:8100/v1/wit/batch/" \
  -H "Content-Type: application/json" \
  -d '{
    "requests": [
      {"persona": "roast", "q": "Dave"},
      {"persona": "commit-message"},
      {"persona": "hot-take", "q": "tabs vs spaces", "mood": "spicy"}
    ]
  }'

Each item is a separate LLM call, so a batch is slower than a single request and counts as one hit against the rate limit.

Replying to posts

POST /v1/wit/reply/ takes a social media post and returns one short, sarcastic reply to it — built for reply bots. The reply reacts to the post's actual content and is kept tweet-length by default (length defaults to short).

curl -X POST "http://localhost:8100/v1/wit/reply/" \
  -H "Content-Type: application/json" \
  -d '{"post": "Just shipped a feature with zero tests. What could go wrong?"}'

See docs/twitter-bot.md for a complete ~30-line Twitter reply bot using this endpoint.

Health and Docs

Endpoint Description
/v1/wit/health/live/ Liveness probe
/v1/wit/health/ready/ Readiness probe
/v1/wit/health/status/ Full health check
/v1/wit/swagger/ Swagger UI
/v1/wit/redoc/ ReDoc

Command-line client

The repo ships a dependency-free CLI that wraps the API. Install the package and use the snark command:

pip install -e .

snark hot-take "tabs vs spaces"
snark roast Dave --mood spicy
snark hot-take "remote work" --stream
snark github torvalds
snark personas
snark stats

The first positional argument is a persona slug (or one of personas, stats, random, roast <name>, github <username>, url <url>); the second is optional context. It points at http://localhost:8100 by default — override with --api or the SNARK_API_URL environment variable.

Use it from AI agents (MCP)

Snark ships an MCP server (snark-mcp, in mcp-server/) so AI assistants — Claude Code, Claude Desktop, Cursor, and other MCP clients — can call snark as native tools. It's a thin client over this same REST API: point it at a running instance with SNARK_API_URL.

It exposes a curated set of tools — snark_roast, snark_roast_github, snark_roast_url, snark_hot_take, snark_commit_message, snark_reply, snark_worth_it, snark_wit (any persona by slug), and snark_list_personas — so you can just ask "roast the GitHub user torvalds" in your assistant and it calls the right one.

Quickest path with Claude Code (per-project, private to you):

make mcp-install   # build the server into mcp-server/.venv
claude mcp add snark -e SNARK_API_URL=http://localhost:8100 \
  -- "$(pwd)/mcp-server/.venv/bin/snark-mcp"

Restart the session and the tools are available. See mcp-server/README.md for Claude Desktop / Cursor configs, the make mcp / make mcp-http / make mcp-inspect run modes, and details.

Architecture

config/
└── providers.yaml     # AI providers: default, fallback order, models, endpoints
snark/
├── base/              # Django config
└── wit/
    ├── models.py      # Persona, ResponseLog, GenerationEvent
    ├── views.py       # API endpoints
    ├── services.py    # Orchestrator (caching, anti-repetition, fallback)
    ├── stats.py       # usage / cost / reliability aggregation
    ├── pricing.py     # per-model token pricing
    ├── providers/     # AI provider abstraction
    │   ├── base.py        # AIProvider interface
    │   ├── config.py      # Loads and validates config/providers.yaml
    │   ├── registry.py    # Provider registry with fallback
    │   ├── openai_compatible.py  # Groq, Ollama, OpenRouter, vLLM, ...
    │   ├── gemini_provider.py
    │   └── claude_provider.py
    └── management/    # seed / admin bootstrap / pricing / retention commands

AI providers

Providers live in config/providers.yaml, not in environment variables. The file ships in the Docker image. With --profile dev the repo is mounted, so editing it and restarting switches providers. With --profile prod you must rebuild the image or mount your own file (see below).

default: groq                  # answers requests
fallback: [groq, gemini, claude]   # tried in order when the default fails

providers:
  groq:                        # the key is the provider name shown in logs and stats
    type: openai_compatible
    url: https://api.groq.com/openai/v1
    key: GROQ_API_KEY          # the NAME of the env var holding the key
    model: openai/gpt-oss-20b
    extra_body:                # merged into every request body
      reasoning_effort: low

  ollama:
    type: openai_compatible
    url: http://host.docker.internal:11434/v1
    model: gemma3:27b
    free: true                 # keyless and costs nothing

  gemini:
    type: gemini
    key: GEMINI_API_KEY
    model: gemini-2.0-flash

  claude:
    type: claude
    key: ANTHROPIC_API_KEY
    model: claude-haiku-4-5-20251001
Field Applies to Description
default top level Provider that answers requests. Must be defined under providers
fallback top level Ordered list of providers to try when the default fails. Optional; no repeats
type every provider openai_compatible, gemini, or claude
model every provider Model id (up to 100 characters)
key every provider Env var name in uppercase (^[A-Z][A-Z0-9_]*$, e.g. GROQ_API_KEY), never the key itself. Required for gemini and claude; omit it for a keyless local server
url openai_compatible Required. Base URL of the OpenAI-compatible API, with no credentials, query or fragment. Must be https when the provider has a key
extra_body openai_compatible Extra JSON fields sent verbatim in every request, e.g. reasoning_effort. Cannot set model, messages, stream, stream_options, max_tokens, temperature, or n
price every provider [input, output] USD per 1M tokens for the dashboard cost estimate
free keyless providers true makes the cost estimate 0. Cannot be combined with key or price

Provider names (the keys under providers) must match ^[a-z][a-z0-9_-]{0,49}$. The file is validated at startup; a bad file stops the app with an error naming the provider and field.

Use your own file. Point AI_PROVIDERS_FILE at it (default: config/providers.yaml). In Docker, mount it into the container and set the variable in .env:

# docker-compose.override.yml
services:
  wit-prod:        # wit-dev already mounts the repo, so it sees edits to config/ directly
    volumes:
      - ./my-providers.yaml:/app/config/my-providers.yaml:ro
AI_PROVIDERS_FILE=/app/config/my-providers.yaml

Fallback. A request goes to default. If it fails (error, rate limit, or an empty reply from an openai_compatible provider), Snark tries each provider in fallback in order, skipping the one that already failed and any provider without an API key. For non-streaming requests, a content-filter refusal first retries the same provider once with a softened prompt. Streaming requests skip that retry and move straight to the next provider, and only if nothing has been sent yet; a stream that fails after output has started does not fall back. If every provider fails the request returns an error.

Network. Requests to openai_compatible providers go straight to the configured url: redirects are not followed, and the OPENAI_* variables (including OPENAI_CUSTOM_HEADERS) and the proxy and CA-bundle variables (HTTPS_PROXY, SSL_CERT_FILE) are ignored. A backend behind a private CA or a forward proxy is not supported.

reasoning_effort. Reasoning models (e.g. gpt-oss) spend part of the token budget thinking. Set the effort through extra_body, as in the Groq entry above. Snark does not retry without it: if your model rejects reasoning_effort, remove it from extra_body.

Pricing. Cost estimates use price or free when set, otherwise the vendored LiteLLM price map, searched for the model id, then <provider name>/<model> (for example groq/openai/gpt-oss-20b). A miss shows cost 0 until you set price or free.

Run with Ollama

  1. Install Ollama on the host and pull a model: ollama pull gemma3:27b.
  2. In config/providers.yaml (or your own file), set default: ollama. The ollama entry is already defined.
  3. On Linux, make Ollama listen on the Docker bridge address (usually 172.17.0.1, see ip addr show docker0) so the container can reach it: OLLAMA_HOST=172.17.0.1 ollama serve. Avoid 0.0.0.0: Ollama has no authentication, so that exposes model inference, pull and delete to anything that can reach the machine. If you must use it, firewall port 11434 to the Docker subnet. Docker Desktop on macOS and Windows needs none of this.
  4. Start the stack: docker compose --profile dev up --build. The compose file already maps host.docker.internal to the host.

Ollama needs no API key. To use a different model, change model to one you have pulled.

Claude through an OpenAI-compatible endpoint? Anthropic's compatibility layer is meant for testing: it ignores reasoning_effort and response_format, and rejects a temperature below 1 on newer models. Use the native claude type instead.

Development

# Run tests
make test

# Run tests with coverage
make test-cov

# Format code
make format

# Lint
make lint

# Run migrations
make migrate

# Seed personas
make seed

# Django shell
make shell

Configuration

Most configuration is via environment variables; AI providers are configured in config/providers.yaml. See .env.example for the full list.

Variable Default Description
AI_PROVIDERS_FILE config/providers.yaml Path to the AI providers file (default provider, fallback order, models, endpoints). See AI providers
LENGTH_TOKENS_SHORT / _MEDIUM / _LONG 60 / 150 / 320 Output-token ceiling per ?length= value
PERSONA_MAX_TOKENS_FLOOR 0 Global floor on every generation's token budget (0=off). Raise to give reasoning models room without editing each persona
GROQ_API_KEY — Groq API key (free at console.groq.com)
GEMINI_API_KEY — Google Gemini API key (optional)
ANTHROPIC_API_KEY — Anthropic API key (optional, paid)
POSTGRES_DB snark PostgreSQL database name
REDIS_DB 9 Redis database number
USE_PROXY_SSL_HEADER False Trust X-Forwarded-Proto when behind a TLS-terminating proxy
NUM_PROXIES — Trusted proxy count, so per-IP rate limiting reads the real client IP from X-Forwarded-For
GUNICORN_WORKERS 2 Gunicorn worker processes (prod only)
GUNICORN_THREADS 8 Threads per worker; threads serve concurrent streams (prod only)
MAX_CONCURRENT_STREAMS 6 Max concurrent SSE streams per worker; excess get a 503 (stream_capacity). 0 disables the cap
ADMIN_ENABLED False Enable the opt-in Django admin UI
ADMIN_URL admin/ Path the admin mounts at when enabled (change it)
ADMIN_USERNAME / ADMIN_EMAIL / ADMIN_PASSWORD — Optional superuser auto-bootstrap on startup
RESPONSE_LOG_RETENTION_DAYS 30 Days to keep raw request logs (ResponseLog); 0 keeps them forever
GENERATION_EVENT_RETENTION_DAYS 90 Days to keep reliability events (GenerationEvent); 0 keeps them forever
LOG_INPUT_MODE redacted How to store user input: redacted (strip structured PII + truncate), none (don't store), or raw (verbatim)
LOG_FORMAT plain Log output: plain (human-readable) or json (one JSON line per record for aggregators)
LOG_LEVEL INFO Root log level: DEBUG, INFO, WARNING, ERROR
GRAFANA_PORT 3000 Host port for Grafana (only used by the observability compose profile)
GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD admin / admin Grafana admin credentials (only used by the observability compose profile)

Behind a reverse proxy? Set USE_PROXY_SSL_HEADER=True and NUM_PROXIES=<n> so HTTPS detection and per-IP rate limiting work correctly. Leave both unset for direct connections.

Streaming & concurrency

Gunicorn threads serve concurrent requests; a ?stream=true request pins a thread for the whole LLM call (streaming bypasses the cache). Tune capacity per deployment with GUNICORN_WORKERS and GUNICORN_THREADS. MAX_CONCURRENT_STREAMS caps streams per worker so a burst of streams can't consume every thread and starve normal JSON traffic — excess streams get a fast 503 with a stream_capacity error and a Retry-After header (set it to 0 to disable the cap). Keep it below GUNICORN_THREADS to leave threads free for JSON requests. For heavy sustained streaming load, an async worker (gevent) or an ASGI server is the future scale-up.

Data retention & privacy

Snark is privacy-forward by default: bounded retention is on out of the box and raw user input is not stored verbatim.

  • Bounded retention. Request logs (ResponseLog) are kept 30 days and reliability events (GenerationEvent) 90 days by default. Tune with RESPONSE_LOG_RETENTION_DAYS / GENERATION_EVENT_RETENTION_DAYS (0 = keep forever).
  • PII-minimized input. LOG_INPUT_MODE=redacted (the default) strips structured PII (email, phone, credit-card, SSN-like) from stored input and truncates it. Set none to store nothing, or raw to store input verbatim (opt-in — users may paste PII). Redaction is dependency-light and catches structured identifiers only, not free-text names/addresses; Microsoft Presidio (NER) is an optional upgrade if you need that.

Pruning runs automatically on startup and can be run on demand or from cron:

make prune-logs DAYS=90          # apply a 90-day window to both tables
# cron (daily at 03:00): apply the configured retention windows
0 3 * * * cd /app/snark && python manage.py prune_logs

Stats and anti-repetition never read the input field, so redaction does not affect them.

Logging & observability

Logs are human-readable plain text by default. Set LOG_FORMAT=json (with an optional LOG_LEVEL, default INFO) to emit one JSON line per record for both Django and app logs — structured so aggregators can filter by field. Recommended in production.

Every generation also logs a structured generation event with provider, model, split input_tokens/output_tokens (and tokens), latency_ms, and an estimated cost_usd — a machine-parseable, per-request usage line. It is INFO and privacy-safe: it never includes raw user input.

LOG_FORMAT=json     # plain (default) | json — one JSON line per record
LOG_LEVEL=INFO      # DEBUG | INFO | WARNING | ERROR

Ship these JSON lines to Loki/Grafana (or any log aggregator) for dashboards and alerting on token usage, cost, latency, and error rates — no extra dependency, just structured stdout.

Every reliability event also emits a structured generation_event line at the single choke point (provider, model, success, fell_back, content_filtered, streamed, error_code, persona) — the stream that powers the reliability dashboards below.

Bundled stack (opt-in)

A ready-to-run Grafana Loki + Grafana Alloy + Grafana stack ships as an opt-in compose profile that is off by default. Alloy scrapes container stdout into Loki; Grafana comes with a provisioned Loki datasource and a starter "snark Reliability" dashboard.

# start snark + the observability stack together (set LOG_FORMAT=json first)
docker compose --profile dev --profile observability up -d      # or --profile prod
# then open Grafana at http://localhost:3000 (default admin / admin)

Config lives under observability/ (Loki, Alloy, and Grafana provisioning), so it is easy to inspect, tweak, or extract. Tune GRAFANA_PORT, GRAFANA_ADMIN_USER, and GRAFANA_ADMIN_PASSWORD in .env. Retention defaults to 7 days in Loki.

Backend-neutral by design. snark itself depends on neither Loki nor Grafana — it only emits standard JSON logs on stdout. The bundled stack is one reference example; to use a different backend (ELK, Datadog, CloudWatch, Grafana Cloud, …), skip the profile and point your own collector at snark's stdout.

Metrics (/metrics, opt-in)

For rate math (error rate, fallback rate) snark can expose a Prometheus /metrics endpoint with a snark_generations_total counter (labels: provider, success, fell_back, content_filtered).

/metrics is served on the same public port as the API, so it is bearer-token authenticated and OFF by default:

  • Set METRICS_AUTH_TOKEN to a long random value to enable it. Empty (the default) means the endpoint returns 404 — it does not exist.
  • Callers must send Authorization: Bearer <token>; a missing or wrong token gets 404 (the endpoint's existence is not revealed). The comparison is constant-time.
  • The bundled observability profile passes the same METRICS_AUTH_TOKEN through to Prometheus automatically, adds a Prometheus datasource, and shows Error rate / Fallback rate panels on the dashboard.

Why a token and not an IP allowlist? On a directly-published port an IP allowlist is unreliable — Docker NAT rewrites external source IPs to the private bridge gateway, and X-Forwarded-For is spoofable. Network isolation (firewall / Kubernetes NetworkPolicy so only your scraper can reach the app) remains the recommended primary control; the token is the app-layer guard on top of it.

Under gunicorn (multi-worker) the profile sets PROMETHEUS_MULTIPROC_DIR on a tmpfs and wires the child_exit hook so scraped values aggregate across workers. Note: with worker recycling (--max-requests) per-process metric files accumulate in that dir; being tmpfs-backed it clears on container restart — raise --max-requests or add a periodic sweep only if it ever grows.

Admin panel (optional)

Snark ships an opt-in Django admin for managing personas and browsing usage — off by default so public deployments never expose a login.

Enable it per deployment in .env:

ADMIN_ENABLED=True
ADMIN_URL=manage-a1b2c3/          # change to a non-guessable path; keep the trailing slash
ADMIN_USERNAME=admin              # optional: auto-bootstrap a superuser on startup
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=a-strong-password  # min 12 chars

Cost pricing comes from a vendored copy of LiteLLM's community-maintained model cost map (MIT), stored at snark/wit/pricing_data.json and refreshable with make update-pricing. Tokens are priced per model, split into input vs output. A provider's price or free setting in config/providers.yaml overrides the map (see AI providers). It remains an estimate — list prices, cached/batch discounts aren't modeled. Both streamed and non-streamed responses log split token usage. Rates are cached for the process lifetime, so restart the app after make update-pricing. Responses logged before the token-split feature carry no input/output split, so their estimated cost shows $0.

On startup the stack runs ensure_admin, which creates/updates the superuser from those vars (a no-op when unset). Or create one manually:

make admin          # interactive createsuperuser
make ensure-admin   # from ADMIN_* env vars

The admin mounts at ADMIN_URL and includes an interactive analytics dashboard (Chart.js) with usage, cost, latency, and reliability analytics — KPI cards, time-series and provider-share charts, latency percentiles, per-persona cost, provider/model breakdowns, reliability metrics (error rate, provider-fallback frequency, content-filter rate) with recent-failure detail, unused-persona detection, a recent-activity feed, DB/Redis health status, and a configurable cost estimate. It also provides persona CRUD with bulk activate/deactivate/duplicate/clear-cache actions, and read-only response-log browsing with retention pruning. Prune logs from the CLI too:

make prune-logs DAYS=90

Keep the admin behind a reverse proxy / IP allowlist on any internet-facing host.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md before submitting a pull request.

By contributing, you agree to the Contributor License Agreement.

Support

snark is free and open source under the AGPL-3.0 license. Running the public API costs real money — every response is a live LLM call. If it made you laugh or saved you time, consider sponsoring its development:

Sponsorship helps keep the public instance running and funds new endpoints.

Security

To report a security vulnerability, please email pramodkodag.dev@gmail.com. Do not open a public GitHub issue. See SECURITY.md for details.

License

Copyright (C) 2026 Pramod Kodag

This project is licensed under the GNU Affero General Public License v3.0. This means:

  • You can use, modify, and distribute this software freely
  • If you modify and deploy it as a network service, you must share your changes
  • All derivative works must use the same license

Author

Pramod Kodag — GitHub

About

Sarcasm as a Service. an AI-powered humor API that generates a unique, witty response on every request. Roasts, honest commit messages, ELI5, and 25+ more endpoints.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

5 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages