# Robust Devs Content API

> OpenAPI 3.1 specification: https://robustdevs.co/openapi.json
> Developer resources hub: https://robustdevs.co/developers

A public, unauthenticated, read-only REST API over the Robust Devs content library. Base URL: `https://robustdevs.co`

No API key. No authentication. Every operation is a `GET`; every response is public information.

## CLI and SDK

```bash
npx robustdevs blog --limit 3          # no install
npm install -g robustdevs              # CLI
npm install robustdevs                 # SDK
```

```js
import { RobustDevsClient } from 'robustdevs';
const client = new RobustDevsClient();
await client.listBlogPosts({ limit: 5 });
```

Source and full documentation: <https://www.npmjs.com/package/robustdevs>

## Versioning

Canonical paths are versioned: `/api/v1/...`. The unversioned `/api/...` paths are stable aliases of the same handlers and are not going away, but new integrations should use `/api/v1/`.

- Breaking changes ship under a new path version (`/api/v2/`). A version is never changed in place.
- Additive changes — new optional fields, new endpoints — are not breaking and ship in place.
- A version scheduled for removal carries `Deprecation` and `Sunset` headers (RFC 9745, RFC 8594), with the sunset date at least **180 days** after the first `Deprecation` header.
- Every response carries an `API-Version` header naming the version that served it — successes and failures alike.
- **No version is deprecated today.** v1 sends neither `Deprecation` nor `Sunset`; that absence is the expected state, not a missing header.
- Full policy: <https://robustdevs.co/deprecation-policy.md>

## Rate limits

Responses carry RFC 9331 headers:

```
RateLimit-Policy: "content";q=120;w=60
RateLimit: "content";r=118;t=57
```

The advertised policy is **120 requests per 60 seconds**. Enforcement is best-effort and per-instance rather than global, so treat the headers as a courtesy signal to self-throttle against, not a security boundary. Exceeding the limit returns `429` with `Retry-After`.

## Sandbox and safety

There is no separate sandbox, because there is nothing to sandbox: every documented operation is a `GET` over already-public content. No call here can mutate, delete or bill anything. Production is the safe environment — exercise it directly.

## Operations

### `getApiIndex` — `GET /api/v1`

The API root. Lists the operations, the error model, the rate-limit policy and where the full contract is published — enough to use the API without reading the OpenAPI document first.

```
curl "https://robustdevs.co/api/v1"
```

### `listBlogPosts` — `GET /api/v1/blogs`

Published blog posts, newest first.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `limit` | integer 1–50 | 3 | How many posts to return |
| `slug` | string | — | Restrict to one post by URL slug |

```
curl "https://robustdevs.co/api/v1/blogs?limit=1"
```

### `listCaseStudies` — `GET /api/v1/case-studies`

Client case studies, newest first, including outcome metrics and tech stack where the client permitted publication.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `limit` | integer 1–50 | 3 | How many case studies to return |
| `slug` | string | — | Restrict to one category by title, e.g. `SaaS`. Matches the category, not a case-study slug. |

```
curl "https://robustdevs.co/api/v1/case-studies?limit=1"
```

## Errors

Every failure returns the same body, served as `application/problem+json` (RFC 9457). The top level is Problem Details; the nested `error` object repeats the same facts in the shape the `robustdevs` npm client reads. Both are always present, and both validate against the `ApiError` schema in the OpenAPI document.

```json
{
  "type": "https://robustdevs.co/api.md#invalid_limit",
  "title": "The \"limit\" parameter must be an integer between 1 and 50.",
  "status": 400,
  "detail": "Received \"999\". Omit the parameter to use the default of 3.",
  "error": {
    "code": "invalid_limit",
    "message": "The \"limit\" parameter must be an integer between 1 and 50.",
    "hint": "Received \"999\". Omit the parameter to use the default of 3.",
    "status": 400,
    "documentation": "https://robustdevs.co/openapi.json"
  }
}
```

`instance` is added when one specific path is at fault, for example an unknown endpoint.

Branch on `error.code` — or equivalently on the `type` URI, which is always this page plus the code as a fragment. Never branch on `title` or `message`: those are prose and may be reworded.

Errors also carry the same `API-Version` and `RateLimit` headers as a success, so a caller that only ever sees failures still learns the version and its remaining quota.

### `invalid_limit`

**400.** The `limit` query parameter was not an integer between 1 and 50. Omit it to take the default of 3.

### `endpoint_not_found`

**404.** No endpoint is published at that path. Fetch <https://robustdevs.co/openapi.json> for the list of operations.

### `rate_limited`

**429.** Too many requests. Wait the number of seconds in `Retry-After`, then retry. See [Rate limits](#rate-limits).

### `internal_error`

**500.** An unexpected server error. Retry once; if it persists, mail hi@robustdevs.co.

### `upstream_unavailable`

**502.** The CMS behind the endpoint could not be reached. Transient — retry in a few seconds.

## Caching

Successful responses carry `Cache-Control: public, max-age=300`. Please respect it.
