Docs · Hormaz Daruwala
API documentation
The Hormaz Daruwala portfolio exposes a public read-only REST API plus MCP and NLWeb surfaces. Base URL is https://hormazdaruwala.vercel.app. Authentication is not required for reads; authenticated entry points return a spec-shaped 401 + WWW-Authenticate hint pointing at RFC 9728 protected-resource metadata. Every error is structured JSON with code, message, and hint fields so agents can recover without scraping HTML.
Endpoints
GET/api/profile
Public profile, roles, availability. No auth.
GET/api/projects?type=client&q=next&limit=10
Filter work by kind and text. Cursor pagination: limit (1-50), cursor, next_cursor, has_more.
GET/api/experience
Experience, hackathons, skills, education.
GET/api/contact
Public contact channels.
POST/api/contact
{name,email,message} validation → 202 + job_id + Location header. Idempotency-Key supported.
GET/api/jobs/{id}
Poll async job until status=completed.
POST/api/batch
Up to 20 allowlisted GET reads in one request. Idempotency-Key supported.
POST/api/agent/key
Self-serve demo API key (free tier; reads are keyless).
GET/api/health
Status + endpoint index.
GET/api/sandbox
Sandbox/test environment description.
POST/ask
NLWeb natural-language ask: {q:"What did Hormaz ship?"}
POST/api/mcp
MCP JSON-RPC: initialize, tools/list, tools/call.
Example requests
curl https://hormazdaruwala.vercel.app/api/profile
curl "https://hormazdaruwala.vercel.app/api/projects?type=personal&q=ai"
curl -X POST https://hormazdaruwala.vercel.app/api/contact \
-H 'Content-Type: application/json' \
-d '{"name":"Ada","email":"ada@example.com","message":"Freelance Next.js build, Q1 timeline"}'
curl -X POST https://hormazdaruwala.vercel.app/ask \
-H 'Content-Type: application/json' \
-d '{"q":"What Web3 work has Hormaz shipped?"}'Versioning & deprecation
URL-path versioning: /api/* is v1 (current) and is mirrored at /api/v1/* with a {version:"v1"} envelope. Every API response carries API-Version: v1 and Deprecation: false headers. Breaking changes ship as a new path version with a 12-month Sunset notice. Rate limits (60 req/min demo) are advertised via RateLimit-Limit/Remaining/Reset headers, with Retry-After on 429.
Idempotency, pagination, async
POST writes (/api/contact, /api/batch, /api/agent/claim, /api/agent/key, /ask) accept an Idempotency-Key header; replays return the original response with Idempotent-Replay: true. List endpoints use cursor pagination (limit, cursor → next_cursor, has_more). Long work is async: POST /api/contact returns 202 + job_id with a Location header — poll GET /api/jobs/{id} until status=completed.
Errors
Errors always return JSON: {"error":{"code":"invalid_email","message":"...","hint":"...","docs":".../docs"}}. Common codes: invalid_json, invalid_name, invalid_email, invalid_message, unauthorized, not_found. Send Accept: text/markdown to receive markdown error bodies on any route including 404s.
Full typed schema: /openapi.json · Agent auth: /auth.md · Source + AGENTS.md: github.com/coderhormaz/Portfolio