# Joel Saji Developer Portal - API, Markdown, and llms.txt

> The read-only JSON API, OpenAPI document, Markdown content negotiation, llms.txt, and rate limits for agents and tools that read joelcsaji.com.

Canonical: https://www.joelcsaji.com/developers

This site is built to be read by people and by software. Everything on this page is public, unauthenticated, and read-only: there is no API key, no signup, and no SDK to install. It works with plain HTTP, with OpenAPI-aware clients, and with agents that call tools from a function schema.

## Base URL

All endpoints are relative to `https://www.joelcsaji.com` and answer over HTTPS only.

## Endpoints

| Method | Path | Operation | What it returns |
| --- | --- | --- | --- |
| GET | `/api/posts` | `listPosts` | List published articles |
| GET | `/api/posts/{slug}` | `getPost` | Get one article |
| GET | `/api/projects` | `listProjects` | List portfolio projects |
| GET | `/api/projects/{id}` | `getProject` | Get one project |
| GET | `/api/now` | `getNow` | Get the /now snapshot |
| GET | `/api/ai-terms` | `searchAiTerms` | Search the AI dictionary |
| GET | `/api/ai-terms/{slug}` | `getAiTerm` | Get one dictionary entry |
| GET | `/api/search-index` | `getSearchIndex` | Get the site search index |

The full contract, including parameter and response schemas, is the OpenAPI 3.1 document at [/openapi.json](/openapi.json). Every operation has a unique `operationId`, a description, typed parameters, and typed responses, so the document can be loaded directly into function-calling toolchains, API clients, or code generators.

## Quickstart

```bash
# The newest article
curl -s https://www.joelcsaji.com/api/posts | jq '.items[0]'

# One article, including its Markdown body
curl -s https://www.joelcsaji.com/api/posts/react-19-deep-dive-server-components-compiler-hooks

# Search the AI dictionary
curl -s 'https://www.joelcsaji.com/api/ai-terms?q=agent&maturity=Rising'

# Portfolio projects and case studies
curl -s https://www.joelcsaji.com/api/projects

# The site search index behind the command palette
curl -s https://www.joelcsaji.com/api/search-index

# The current now-page snapshot
curl -s https://www.joelcsaji.com/api/now

# The machine-readable contract
curl -s https://www.joelcsaji.com/openapi.json
```

List responses have the shape `{ "items": [...], "total": n }`, where `n` counts everything the filters matched rather than just this page, so a paging client can read `total` and `totalPages` without guessing. Single resources are returned as a bare object. Every JSON response carries `Access-Control-Allow-Origin: *`, so browser-based tools can call the API directly.

## Markdown for every page

Every HTML page on this site is also available as Markdown. Send `Accept: text/markdown`, or fetch the same URL with `.md` appended, and the response is the page's content without navigation, styles, or scripts:

```bash
curl -H 'Accept: text/markdown' https://www.joelcsaji.com/about
curl -s https://www.joelcsaji.com/about.md
curl -H 'Accept: text/markdown' https://www.joelcsaji.com/blog/react-19-deep-dive-server-components-compiler-hooks
```

Markdown responses use `Content-Type: text/markdown; charset=utf-8`. Every page response, HTML or Markdown, carries `Vary: Accept` so caches keep the two apart. Quality values are honoured (`text/markdown;q=0.5, text/html` still returns HTML), and a client that accepts neither HTML nor Markdown receives `406 Not Acceptable`.

## Rate limits

Each client may make 60 API requests per 60 seconds. Every API response reports the quota using the IETF RateLimit header fields:

```http
RateLimit-Policy: "default";q=60;w=60
RateLimit: "default";r=59;t=60
```

`q` is the quota, `w` the window in seconds, `r` the remaining requests, and `t` the seconds until the window resets. Once the quota is exhausted the API answers `429 Too Many Requests` with a `Retry-After` header in seconds; wait that long and retry. Limits apply per client and are not tied to any account.

## Errors

Errors are JSON with the same shape everywhere, including unknown `/api/*` paths and unsupported methods:

```json
{
  "error": {
    "code": "not_found",
    "message": "Post \"nope\" was not found.",
    "hint": "List the available items at https://www.joelcsaji.com/api/posts."
  }
}
```

| Status | `code` | When |
| --- | --- | --- |
| 400 | `invalid_parameter` | A query parameter has an unsupported value; the hint lists accepted values. |
| 404 | `not_found` | No item matches the identifier, or no endpoint exists at the path. |
| 405 | `method_not_allowed` | Only `GET` and `HEAD` are supported. |
| 429 | `rate_limited` | The quota above was exceeded; honour `Retry-After`. |
| 500 | `internal_error` | Something failed on the server; retry later. |

## Machine-readable files

- [/llms.txt](/llms.txt): a summary of the site for language models, with guidance on when to use it.
- [/llms-full.txt](/llms-full.txt): the about page, every portfolio case study, and all articles as one Markdown document.
- [/openapi.json](/openapi.json): the OpenAPI 3.1 contract for the JSON API.
- [/sitemap.xml](/sitemap.xml): every public URL, with a `lastmod` date where the content has a real one.
- [/rss.xml](/rss.xml): new articles as they publish.
- [/feed.json](/feed.json): the same articles as a JSON Feed 1.1 document.
- [/robots.txt](/robots.txt): crawler guidance. Search engines, AI assistants, and agents are welcome.
- [/.well-known/ard.json](/.well-known/ard.json): the Agentic Resource Discovery catalog listing these endpoints.
- [/.well-known/security.txt](/.well-known/security.txt): how to report a security issue.

## Attribution and contact

Content is written by Joel Saji. Cite the canonical URL of the page you drew from. There is no official CLI or client library; the API is small enough to call directly. Questions, broken responses, or ideas for the API: email [hello@joelcsaji.com](mailto:hello@joelcsaji.com) or use the [contact page](/contact).
