API

Everything on this site is data first. These endpoints return the same records the pages are built from. All of them are static JSON generated at build time, except /api/ask, which turns a question into SQL, /api/questions, which passes on a question a visitor chose to send, and /api/stats, which reports the counters. Rate limits: 10 questions a minute per address, shared by /api/ask and /api/questions, and a monthly cap; when the cap is reached the endpoint returns 503 with reason "budget".

A question to /api/ask is cached for a month by its wording, so repeat questions are free. The monthly cap counts model calls, not questions, in the UTC calendar month.

Try it:

curl https://alexkachur.com/api/projects.json

The whole API as an OpenAPI 3.1 document, built from the same schema and endpoint list, for loading into any client: /api/openapi.json

GET /api/projects.json

The projects on this site, in site order

curl https://alexkachur.com/api/projects.json
[
  {
    "id": 1,
    "slug": "splitroof-ai-assistant",
    "name": "SplitRoof AI assistant",
    "kind": "team",
    "summary": "An assistant inside a shared-household expense app that answers question...",
    "role": "AI integration, documentation, schedule",
    "year_start": 2026,
    "year_end": null,
    "client_name": null,
    "live_url": null,
    "repo_url": "https://github.com/AlexKachur98/splitroof-assistant-spike",
    "has_live_demo": 0,
    "uses_llm": 1,
    "llm_job": "answers questions about a household's shared expenses by calling typed t...",
    "paid": 0,
    "featured": 1,
    "card": "A read-only assistant that answers \"who owes what this month\" from a hou...",
    "team": "group project, COMP 231 Software Development Project, Fall 2026",
    "highlights": "Own the assistant for a shared-household expense app: a read-only agent ..."
  },
  ...
]

GET /api/projects/{slug}.json

curl https://alexkachur.com/api/projects/splitroof-ai-assistant.json
{
  "id": 1,
  "slug": "splitroof-ai-assistant",
  "name": "SplitRoof AI assistant",
  "kind": "team",
  "summary": "An assistant inside a shared-household expense app that answers question...",
  "role": "AI integration, documentation, schedule",
  "year_start": 2026,
  "year_end": null,
  "client_name": null,
  "live_url": null,
  "repo_url": "https://github.com/AlexKachur98/splitroof-assistant-spike",
  "has_live_demo": 0,
  "uses_llm": 1,
  "llm_job": "answers questions about a household's shared expenses by calling typed t...",
  "paid": 0,
  "featured": 1,
  "card": "A read-only assistant that answers \"who owes what this month\" from a hou...",
  "team": "group project, COMP 231 Software Development Project, Fall 2026",
  "highlights": "Own the assistant for a shared-household expense app: a read-only agent ...",
  "technologies": [
    "Anthropic API",
    "Express",
    ...
  ]
}

GET /api/facts.json

Single facts about Alex, one per row

curl https://alexkachur.com/api/facts.json
[
  {
    "key": "available_from",
    "value": "2027-05",
    "description": "The month Alex can start full-time work, as YYYY-MM"
  },
  {
    "key": "email",
    "value": "alexkachur98@gmail.com",
    "description": "Alex's email address"
  },
  ...
]

GET /api/technologies.json

Languages, frameworks, libraries and services Alex has used

curl https://alexkachur.com/api/technologies.json
[
  {
    "id": 1,
    "name": ".NET",
    "category": "framework",
    "core": 0,
    "skill_area": "Backend"
  },
  {
    "id": 2,
    "name": "Anthropic API",
    "category": "ai",
    "core": 1,
    "skill_area": "LLM integration"
  },
  {
    "id": 3,
    "name": "Astro",
    "category": "framework",
    "core": 0,
    "skill_area": "Frontend"
  },
  ...
]

GET /api/project_images.json

The screenshots on each case study, in display order

curl https://alexkachur.com/api/project_images.json
[
  {
    "project_id": 1,
    "position": 1,
    "alt": "A terminal in VS Code after npm test in splitroof-assistant-spike, with ...",
    "caption": null
  },
  {
    "project_id": 1,
    "position": 2,
    "alt": "VS Code with src/assistant.js open on the system prompt, which tells the...",
    "caption": null
  },
  ...
]

GET /api/courses.json

Courses Alex is taking at Centennial College

curl https://alexkachur.com/api/courses.json
[
  {
    "code": "COMP 231",
    "name": "Software Development Project 1",
    "term": "Fall 2026",
    "topics": "A team project taken from requirements to a working, tested and document..."
  },
  {
    "code": "COMP 255",
    "name": "Business and Entrepreneurship for SET",
    "term": "Fall 2026",
    "topics": "How businesses are organised and run, ethics and privacy, and turning so..."
  },
  ...
]

GET /api/timeline.json

Work, education and project events by date

curl https://alexkachur.com/api/timeline.json
[
  {
    "id": 1,
    "date": "2019-08",
    "kind": "work",
    "event": "Manager, IOAL Distributing"
  },
  {
    "id": 2,
    "date": "2022-01",
    "kind": "work",
    "event": "QA Tester, 360 Plus IT Consulting"
  },
  ...
]

GET /api/pets.json

The cats Alex lives with

curl https://alexkachur.com/api/pets.json
[
  {
    "name": "Moura",
    "species": "cat",
    "breed": "Norwegian Forest cat",
    "born": 2014,
    "personality": "makes you earn her love, then becomes your best friend",
    "photo_url": "/images/pets/moura.webp",
    "photo_alt": "Close-up of Moura on the cat tree, pink nose forward"
  },
  ...
]

GET /api/experience.json

Jobs and roles Alex has held

curl https://alexkachur.com/api/experience.json
[
  {
    "id": 1,
    "title": "Manager",
    "organization": "IOAL Distributing",
    "location": "Mississauga, ON",
    "start": "2019-08",
    "end": "2022-01",
    "summary": "Warehouse operations.",
    "highlights": "Ran daily warehouse operations (inventory control, order fulfilment, ven..."
  },
  ...
]

GET /api/interests.json

Alex's hobbies and favourites, in his words

curl https://alexkachur.com/api/interests.json
[
  {
    "id": 1,
    "category": "video game",
    "name": "Counter-Strike",
    "note": "My competitive game. I was the in-game leader (IGL) for a semi-pro team ..."
  },
  {
    "id": 2,
    "category": "video game",
    "name": "The Elder Scrolls V: Skyrim",
    "note": "Rich characters and stories, and endless ways to play. Around 3,000 hour..."
  },
  ...
]

GET /api/storage.json

What this site stores for a visitor or a question, how long, and why, from the constants the code uses

curl https://alexkachur.com/api/storage.json
[
  {
    "item": "The SQL and the model's one-line explanation for a question, which can r...",
    "kept_for": "30 days",
    "purpose": "Repeat questions are answered from the cache, free"
  },
  {
    "item": "The model's reason a question cannot be answered, which can repeat words...",
    "kept_for": "1 day",
    "purpose": "The same, for questions the site cannot answer"
  },
  ...
]

GET /api/uses.json

The machines, peripherals and software Alex uses, and what he is learning, as listed on /uses

curl https://alexkachur.com/api/uses.json
[
  {
    "position": 1,
    "section": "Machines",
    "item": "Main PC",
    "details": "ASUS ROG Astral GeForce RTX 5090, AMD Ryzen 7 9850X3D, ASUS ROG Strix X8..."
  },
  {
    "position": 2,
    "section": "Machines",
    "item": "Second PC",
    "details": "GeForce RTX 3080, Intel Core i9-12900K. Also built myself; now mostly fo..."
  },
  ...
]

GET /api/sections.json

The text of the site's pages and case studies, one row per section

curl https://alexkachur.com/api/sections.json
[
  {
    "page": "/#about",
    "position": 1,
    "heading": "About",
    "body": "I build full-stack web apps, and I like adding AI that solves real-world..."
  },
  ...
]

GET /api/page_images.json

The photos on the site's other pages

curl https://alexkachur.com/api/page_images.json
[
  {
    "page": "/#about",
    "position": 1,
    "alt": "Moura on the top bed of a cat tree and Simba on the lower one, in window...",
    "caption": "Moura takes the top bed and Simba takes what is left."
  },
  {
    "page": "/404",
    "position": 1,
    "alt": "Simba mid-meow on a patterned rug, mouth wide open",
    "caption": "Simba, reacting to the URL you typed."
  }
]

GET /api/schema.json

curl https://alexkachur.com/api/schema.json
{
  "hash": "4b6fe712744e9660a177de49ea027df5103669b55f213f6132e743eb5813...",
  "ddl": "-- Single facts about Alex, one per row\nCREATE TABLE facts (...",
  "tables": [
    {
      "name": "facts",
      "description": "Single facts about Alex, one per row",
      "primaryKey": [
        "key"
      ],
      "columns": [
        {
          "name": "key",
          "type": "TEXT",
          "nullable": false,
          "unique": false,
          "description": "Fact name, for example location or available_from"
        },
        ...
      ]
    },
    ...
  ],
  "factKeys": [
    "available_from",
    ...
  ],
  "facts": [
    {
      "key": "available_from",
      "description": "The month Alex can start full-time work, as YYYY-MM"
    },
    ...
  ],
  "sectionPages": [
    "/#about",
    ...
  ],
  "sectionHeadings": [
    "About",
    ...
  ],
  "photoAlt": {
    "/images/pets/moura.webp": "Close-up of Moura on the cat tree, pink nose forward",
    "/images/pets/simba.webp": "Simba peeking out from between grey couch cushions"
  }
}

GET /api/resume.json

Alex's resume in the JSON Resume format, built from the same tables.

curl https://alexkachur.com/api/resume.json
{
  "basics": {
    "name": "Alex Kachur",
    "label": "Full-stack & AI developer",
    "email": "alexkachur98@gmail.com",
    "url": "https://alexkachur.com",
    "summary": "I build web apps with an AI doing real work inside them.",
    "location": {
      "city": "Toronto"
    },
    "profiles": [
      {
        "network": "GitHub",
        "username": "AlexKachur98",
        "url": "https://github.com/AlexKachur98"
      },
      ...
    ]
  },
  "work": [
    {
      "name": "Self-employed",
      "position": "Freelance Web Developer",
      "location": "Thornhill, ON",
      "startDate": "2026",
      "summary": "Uraz Hoops (urazhoops.com); Think Smarter Insurance review f...",
      "highlights": [
        "Built and handed off a five-page marketing site for Uraz Hoo...",
        ...
      ]
    },
    ...
  ],
  "education": [
    {
      "institution": "Centennial College",
      "area": "Software Engineering Technology",
      "studyType": "Advanced Diploma",
      "startDate": "2025-01",
      "endDate": "2027-04",
      "score": "4.4 / 4.5",
      "courses": [
        "COMP 231 Software Development Project 1",
        ...
      ]
    }
  ],
  "skills": [
    {
      "name": "Languages",
      "keywords": [
        "SQL",
        ...
      ]
    },
    ...
  ],
  "languages": [
    {
      "language": "English",
      "fluency": "Fluent"
    },
    ...
  ],
  "projects": [
    {
      "name": "SplitRoof AI assistant",
      "description": "An assistant inside a shared-household expense app that answ...",
      "highlights": [
        "Own the assistant for a shared-household expense app: a read...",
        ...
      ],
      "keywords": [
        "Anthropic API",
        ...
      ],
      "startDate": "2026",
      "roles": [
        "AI integration, documentation, schedule"
      ],
      "url": "https://github.com/AlexKachur98/splitroof-assistant-spike"
    },
    ...
  ],
  "interests": [
    {
      "name": "video game",
      "keywords": [
        "Counter-Strike",
        ...
      ]
    },
    ...
  ]
}

POST /api/ask

curl -X POST https://alexkachur.com/api/ask -H 'content-type: application/json' -d '{"question":"What has Alex built for paying clients?"}'

Each answer also carries a token: to send that question to Alex, pass it to /api/questions within 10 minutes.

What is stored

SELECT item, kept_for, purpose FROM storage;
itemkept_forpurpose
The SQL and the model's one-line explanation for a question, which can repeat words from it, under a SHA-256 hash of the question30 daysRepeat questions are answered from the cache, free
The model's reason a question cannot be answered, which can repeat words from it, under a SHA-256 hash of the question1 dayThe same, for questions the site cannot answer
A counter keyed by a scrambled form of your address2 minutes 1 secondThe rate limit, for questions and sent questions alike
Two monthly totals: questions answered and model calls40 daysThe footer and the monthly cap
A question you chose to send to Alex, and the day you sent it90 daysSo Alex can add what is missing
The number of questions sent today2 daysThe daily cap on sent questions
Your light or dark theme choice, in your own browser, never sent to this siteuntil you clear itKeeps the theme you picked across pages

Everything in the table but the theme choice is held in Upstash Redis.

Your address is scrambled with a keyed hash (HMAC-SHA-256) before anything is stored: a plain hash of an address could be reversed by hashing every address in turn, but without the site's secret key the stored value cannot be matched back to an address.

One line per failed request: status, error type, request ID and timing, written to Vercel's logs. Never the question or your address.

Anthropic, which runs the model, receives the question and, under its data retention policy, deletes API inputs and outputs within 30 days, or keeps them up to 2 years if its safety systems flag a request; it does not train on them, since this site's account has not opted in to share data.

Vercel, which hosts the site, keeps its request logs, that failed-request line included, for one hour on this site's plan, and counts page views for Web Analytics without cookies, discarding the visitor hash after 24 hours.

POST /api/questions

Stores a question the site could not answer, for Alex to read, only when the visitor chooses to send it. It needs the token /api/ask returned with that question, no older than 10 minutes, and keeps only the question and the day it was sent, for 90 days.

curl -X POST https://alexkachur.com/api/questions -H 'content-type: application/json' -d '{"question":"A question the site could not answer","token":"<token from /api/ask>"}'
{
  "sent": true
}

GET /api/stats

curl https://alexkachur.com/api/stats