Site JSON API
The site's content is also JSON
Everything the pages render is served over public, cacheable endpoints. Reads are open with CORS enabled, so you can pull this data from any client. The same { error } shape backs every failure response.
Endpoints
/api/projectsEvery project as a light record. Case studies are linked, not embedded.
/api/projects/saferouteOne project, full record including its case study
/api/journalEvery post with a derived word count. Content blocks are linked, not embedded.
/api/journal/serverless-incident-pipelineOne post, full record including content blocks
/api/contactContact form intake. JSON body: name, email, message. Rate limited.
/api/messagesStored contact submissions. Requires an admin bearer token.
Try it
# List every project
curl -s https://aliutijani.com/api/projects | jq .count
# Pull one case study
curl -s https://aliutijani.com/api/projects/saferoute | jq .caseStudy
# Journal posts, word counts included
curl -s https://aliutijani.com/api/journal | jq '.items[] | {slug, wordCount, readingMinutes}'Response shapes
GET /api/projects
{
"count": 8,
"items": [
{ "slug": "saferoute", "name": "SafeRoute", "tags": [...],
"period": "...", "featured": true, "hasCaseStudy": true, ... }
]
}
GET /api/projects/saferoute
{
"slug": "saferoute",
"name": "SafeRoute",
"overview": ["...", "..."],
"caseStudy": { "problem": [...], "implementation": [...], "outcome": "..." },
"tech": [...],
"links": {...}
}Not found (any endpoint, 404)
{ "error": "Project not found. See /api/projects for the full list." }Contact and messages
POST /api/contact
Accepts JSON and stores the submission in Postgres before attempting email delivery. If delivery fails, the message survives and I still see it. Five requests per ten minutes per IP, tracked in the database.
GET /api/messages
Reads stored submissions. Send Authorization: Bearer <token> where the token is the server's ADMIN_TOKEN. Unguarded requests get a 401; responses are never cached.