# Portal for developers and agents

> Everything this website publishes about me is also available as JSON, as OpenAPI and as markdown. No keys, no sign-up and with open CORS.

Developer portal for sgomez.dev: a public REST API with no authentication, an OpenAPI 3.1 specification, JSON errors, markdown content negotiation and instruction files for agents.

Canonical URL: https://sgomez.dev/en/developers

## Quickstart

Three calls and you have the whole map. Check that the API responds, read it from its specification and ask for the profile.

```bash
curl -s https://sgomez.dev/api/v1/health?lang=en
curl -s https://sgomez.dev/openapi.json
curl -s https://sgomez.dev/api/v1/profile?lang=en
```

There is no separate sandbox or test keys, because the API is read-only and all of its content is already public, so the production environment IS the test environment. There is nothing you can break with a GET. The API answers in Spanish by default and accepts `?lang=en` or `Accept-Language: en` for English.

## Endpoints

| Method and path | operationId | What it returns |
| --- | --- | --- |
| GET /api/v1/health | getHealth | Service status and entry links. |
| GET /api/v1/profile | getProfile | Identity, role, location, contact and availability. |
| GET /api/v1/about | getAbout | Long biography and year-by-year timeline. |
| GET /api/v1/projects | listProjects | Published projects, with stack and link. |
| GET /api/v1/projects/{slug} | getProject | A single project by its slug. |
| GET /api/v1/experience | listExperience | Positions, organizations and periods. |
| GET /api/v1/skills | listSkills | Technologies by category, with years of use. |
| GET /api/v1/certifications | listCertifications | Certifications with a link to the credential. |
| GET /api/v1/education | listEducation | Formal education. |
| GET /api/v1/recommendations | listRecommendations | Recommendations written by colleagues and clients. |
| GET /api/v1/search?q= | searchContent | Keyword search over all of the above. |

Collections accept `limit` (1-100) and `offset` (0-1000). `search` accepts `q` (required) and `limit` (1-50). Every successful response is wrapped in `{ "data": …, "meta": … }`, and `meta` includes `count`, `total`, `self` and `documentation_url`.

## Errors

Errors are JSON too, always in the same envelope. `code` is stable and can be used in a `switch`; `hint` says what to do to fix it, which is what an agent lacks when it gets an empty 404.

```json
{
  "error": {
    "status": 404,
    "code": "not_found",
    "message": "No project with slug \"nope\".",
    "hint": "Known slugs: nudaui, sortlab, … List them with GET /api/v1/projects.",
    "documentation_url": "https://sgomez.dev/developers"
  }
}
```

- `400 invalid_parameter`: a parameter is missing or out of range.
- `404 not_found`: the resource or the endpoint does not exist. Any unknown route under /api responds with JSON, never HTML.
- `405 method_not_allowed`: the API is read-only; the response includes the `Allow` header.

## Authentication and limits

There is no authentication or API keys because there is no private data behind it, so a key would only be red tape. There is no per-client rate limit either, beyond the CDN's ordinary protection. In exchange, responses are served cached (`Cache-Control: public, max-age=300, s-maxage=3600`). If you need the whole catalog, one call per collection is enough, and repeating the same call in a loop won't give you fresher data.

CORS is open to any origin (`Access-Control-Allow-Origin: *`) for GET, HEAD and OPTIONS, so the API can be called from the browser. The data is published under the CC BY 4.0 license, so you can use it with attribution.

## OpenAPI specification

The specification is OpenAPI 3.1 and is generated from the same code that serves the endpoints, so it cannot describe a route that no longer exists. Each operation has a unique `operationId`, a `summary`, a `description`, typed parameters and a response schema per status code, which is exactly what a function-calling client needs to turn it into tools.

- [/openapi.json](https://sgomez.dev/openapi.json): Canonical location.
- [/api/openapi.json](https://sgomez.dev/api/openapi.json): The same document under /api.
- [/api/openapi.yaml](https://sgomez.dev/api/openapi.yaml): The same document as YAML.

## Markdown for agents

Content pages are served as markdown when the request asks for it, following the acceptmarkdown.com convention. The canonical URL does not change and the response carries `Vary: Accept`, so a CDN does not hand an agent the HTML variant it cached for a browser.

```bash
curl -s -H "Accept: text/markdown" https://sgomez.dev/en/about

# or, if you prefer an explicit URL:
curl -s https://sgomez.dev/en/about.md
```

It works on `/`, `/about`, `/contact`, `/privacy` and `/developers`, and on their English twins under `/en/…`. Routes that are already markdown (`/llms.txt`, `/agents.md`) are served as they are. A route that does not exist returns 404 with a markdown body that says where to go, instead of an error page an agent can't read.

## Files for agents

- [/llms.txt](https://sgomez.dev/en/llms.txt): Factual summary of the site, with the “when to use this” section.
- [/agents.md](https://sgomez.dev/en/agents.md): Usage instructions covering what this site is for and how to call it.
- [/sitemap.xml](https://sgomez.dev/sitemap.xml): All published URLs.
- [/robots.txt](https://sgomez.dev/robots.txt): AI crawlers explicitly allowed.
- [/manifest.webmanifest](https://sgomez.dev/manifest.webmanifest): Web app manifest.

## Versioning

The version is in the path (`/api/v1`). Within v1 only fields and endpoints are added, because removing a field or renaming an `operationId` would be a breaking change and would ship as `/api/v2`. Error `code` values are part of the contract and are not renamed.

---

More machine-readable formats: [llms.txt](https://sgomez.dev/en/llms.txt), [agents.md](https://sgomez.dev/en/agents.md), [OpenAPI](https://sgomez.dev/openapi.json), [sitemap](https://sgomez.dev/sitemap.xml).
