# Pre-render images from CI

Mocco renders an image the first time it is loaded. Rendering takes a moment, and some apps give up on a slow image and show the link without one. When you publish a page, you can have CI render its image right away, so it is already stored when the first crawler comes.

CI doesn't need the signing secret for this: Mocco signs with the project's stored secret and gives back the signed URL. The project still needs a secret (see [The signing secret](https://www.mocco.club/docs/en/articles/27m78b-overview#the-signing-secret)).

## 1. Create a key

An owner or admin opens the project's **API keys** and creates a **secret** key with `og:write` ("pre-render OG images from CI and get their signed URLs"). Store it as a CI secret, for example `MOCCO_API_KEY`.

## 2. Render

```sh
curl -X POST https://api.mocco.club/v1/og/images \
  -H "Authorization: Bearer $MOCCO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"template":"article","params":{"brand":"Acme Docs","eyebrow":"Guides","title":"Install in five minutes"}}'
```

`params` are the template's fields as text, the same ones a signed URL takes (see [Templates](https://www.mocco.club/docs/en/articles/27m78b-overview#templates)), without `s`. Mocco answers:

```json
{ "url": "https://api.mocco.club/v1/og/<project id>/article.png?brand=…&s=…", "hash": "<64 hex characters>" }
```

Put `url` in the page's `og:image`. It is the same URL that `@mocco/node` and the console sign for the same fields, and loading it is now served from storage. Sending the same request again renders nothing and returns the same answer, so it is safe to run on every deploy.

## What Mocco answers

| Situation | Answer |
| --- | --- |
| No key, a publishable key, or a secret key without `og:write` | `401` or `403` |
| An unknown template, or the project has no signing secret | `404` |
| A required field missing, a field the template doesn't take (`s` included), a value over its limit, a bad `accent`, or a body that isn't `{ template, params }` with text values | `400`, naming the field |
| A body over 16 KiB | `413` |
| More than 60 new renders a minute for the project (images already stored don't count) | `429`, with `Retry-After` |
| Otherwise | `200` with `url` and `hash` |

The 60 renders a minute are shared with images loaded through signed URLs. If you pre-render many pages at once, wait for `Retry-After` and continue.
