# Andrew Campi developer resources

This is the developer and agent guide for andrewcampi.com. The site is Andrew Campi's portfolio. It is free to read. There is no API key, no signup, no sandbox tenant, and no paid tier. The only scope is `portfolio:read`, and every caller already has it. Do not send an `Authorization` header. A token will not be issued.

`robots.txt` allows GPTBot, ClaudeBot, ChatGPT-User, PerplexityBot, Google-Extended, and Applebot-Extended, along with every other user agent. A challenge page in front of the host can still stop a crawler before it reads that file. The published intent of this site is that those agents may fetch the public pages.

## When to use this

Use these URLs when you need Andrew Campi's current role, the Fiserv dates, or a factual summary of a project written up here (System 1 Server, Cecropia, Vessel, VulnMap, and the rest of the sidebar). Do not use them for Comft or Fiserv product support, account access, or anything that was never published on this domain.

## Markdown negotiation

Every documentation URL supports content negotiation on `GET`.

```bash
curl -sS -D - -o /dev/null -H 'Accept: text/markdown' https://andrewcampi.com/
curl -sS -D - -o /dev/null -H 'Accept: text/html' https://andrewcampi.com/
```

`Accept: text/markdown` returns a nonempty markdown body with `Content-Type: text/markdown; charset=utf-8` and `Vary: Accept`. `Accept: text/html` returns the HTML page, also with `Vary: Accept`. The homepage markdown is the introduction. A project such as `https://andrewcampi.com/cecropia.html` returns `wiki/cecropia.md` under the same rule. You can also fetch the files directly:

- Index for agents: [https://andrewcampi.com/llms.txt](https://andrewcampi.com/llms.txt)
- Full corpus: [https://andrewcampi.com/llms-full.txt](https://andrewcampi.com/llms-full.txt)
- Raw pages: `https://andrewcampi.com/wiki/<slug>.md`

`llms.txt` follows the usual shape: an H1, a short blockquote, a "When to use this" section, then links. `llms-full.txt` is every page concatenated in sidebar order.

## JSON API

The API is read-only JSON. The OpenAPI document is [https://andrewcampi.com/openapi.json](https://andrewcampi.com/openapi.json). Protected-resource metadata, including `scopes_supported: ["portfolio:read"]`, is at [https://andrewcampi.com/.well-known/oauth-protected-resource](https://andrewcampi.com/.well-known/oauth-protected-resource). That file does not point at an authorization server. There isn't one.

| Method | Path | operationId | Returns |
| --- | --- | --- | --- |
| GET | `/api/v1` | `getApiDirectory` | Endpoint list and the public-scope note |
| GET | `/api/v1/profile` | `getProfile` | Name, role, location, education, experience |
| GET | `/api/v1/experience` | `getExperience` | The experience array only |
| GET | `/api/v1/projects` | `listProjects` | Project summaries |
| GET | `/api/v1/projects/{slug}` | `getProject` | One project |

```bash
curl -sS https://andrewcampi.com/api/v1/profile
```

Errors are JSON, including 404, 405, and 429:

```json
{
  "error": {
    "code": "not_found",
    "message": "No project named 'missing'.",
    "hint": "GET /api/v1/projects lists every public project slug."
  }
}
```

Successful responses send `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`. The limit is 120 requests per minute per client IP, counted separately from the MCP endpoint. A 429 also sends `Retry-After` (seconds) and uses the error code `rate_limited`. Wait that many seconds and retry the same GET. `POST` is rejected with `method_not_allowed`.

Comft's current role is present in the experience array with an empty `highlights` list. That is intentional. There is no public description of the job yet. Fiserv's `end` is `2026-06`.

## Versioning and deprecation

The version is the path prefix `/api/v1`. How a later version would be retired, including when a response would carry `Deprecation` and `Sunset` headers, is written in [Andrew Campi API versioning and deprecation policy](https://andrewcampi.com/wiki/api-versioning.md). `/api/v1` is the current version, so those headers are not sent today. Every API response links to that policy with `Link: <https://andrewcampi.com/api-versioning.html>; rel="describedby"`.

## Missing pages

A URL that is not a published page, static file, or API route returns HTTP 404. It does not return the homepage. Send `Accept: text/markdown` and the body is a short markdown explanation that points at [llms.txt](https://andrewcampi.com/llms.txt), this guide, and [sitemap.xml](https://andrewcampi.com/sitemap.xml). The same pages are also served at extensionless paths: `/developers`, `/docs`, `/about`, `/contact`, `/privacy`, and `/api-versioning`.

## MCP

A read-only [Model Context Protocol](https://modelcontextprotocol.io) server is at `https://andrewcampi.com/mcp`. The transport is Streamable HTTP. `POST` a JSON-RPC 2.0 body. `GET` returns 405 because this server does not open an SSE side channel. `DELETE` ends a session with 204. After `initialize`, the response includes an `Mcp-Session-Id` header. Later calls may send it back. Calls without it still work.

The discovery files are:

- [https://andrewcampi.com/.well-known/mcp/manifest.json](https://andrewcampi.com/.well-known/mcp/manifest.json)
- [https://andrewcampi.com/.well-known/ai-catalog.json](https://andrewcampi.com/.well-known/ai-catalog.json)

Tools:

- `get_profile` reads the same object as `GET /api/v1/profile`.
- `list_projects` reads the project list.
- `get_page` takes `{ "slug": "cecropia" }` and returns that page's markdown. Slugs match the HTML file names (`work-experience`, `system1-server`, and so on).

Each tool is marked read-only. None of them change the site.

## What this site does not publish

There is no official portfolio CLI on npm, PyPI, or Homebrew. Natural and Otto are separate projects, not clients for this domain. There is no OAuth authorization server, no API key screen, and no trial signup. The sitemap of the HTML pages is [https://andrewcampi.com/sitemap.xml](https://andrewcampi.com/sitemap.xml).
