Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/server/WebUiServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ class WebUiServer {
});

this.http.addStaticPath(path.resolve(__dirname, '..', 'public'));
// HTTP API documentation: HTML and Markdown references (generated by `npm run docs:api`) and the OpenAPI spec they are built from
const apiDocsDirectory = path.resolve(__dirname, 'api-docs');
this.http.routerStatics.get(['/api-docs', '/api-docs/'], (_request, response) => {
response.type('text/html; charset=utf-8').sendFile(path.join(apiDocsDirectory, 'index.html'));
});
this.http.routerStatics.get('/api-docs/API.md', (_request, response) => {
response.type('text/plain; charset=utf-8').sendFile(path.join(apiDocsDirectory, 'API.md'));
});
this.http.routerStatics.get('/api-docs/openapi.yaml', (_request, response) => {
response.type('text/yaml; charset=utf-8').sendFile(path.join(apiDocsDirectory, 'openapi.yaml'));
});
buildEndpoints(this.http);
}

Expand Down
220 changes: 220 additions & 0 deletions lib/server/api-docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<!-- GENERATED by scripts/build-api-docs.js from openapi.yaml, do not edit. Run `npm run docs:api` -->
# Bookkeeping HTTP API

HTTP API of the ALICE Bookkeeping. **Internal use only.**

> Pilot: only the `tags` resource is documented so far.

## Authentication
Every request carries a JWT in the `token` **query parameter** (e.g. `GET /api/tags?token=<jwt>`).
The token's `access` claim lists the user's roles; operations marked with a role below return `403` otherwise.

## Response envelope
Successful responses are `{ "data": ... }`, with `meta.page` added on paginated endpoints.
Failed responses are `{ "errors": [ { status, title, detail?, source? } ] }`.
Note that request-validation failures are sent with **HTTP 400** while each error's `status` field says `"422"`.

## Conventions
- Filters use bracket syntax: `filter[partialText]=foo`. List-valued filters are comma separated: `filter[ids]=1,2,3`.
- Pagination uses `page[limit]` and `page[offset]`.
- Timestamps are Unix epoch **milliseconds**.
- Operations flagged `x-llm-exclude: true` change data and are **not meant for LLMs or MCP tools**; skip them when generating tools.

## Instructions for LLMs and automated agents
- **Always paginate** when an endpoint supports `page[limit]` and `page[offset]`. Never rely on the default: when `page[limit]` is omitted the server
may return up to 5000 items, which is far too much for a model context.
- **Request at most 100 items per call** (`page[limit]=100`). This is a rule for LLMs and agents, not a server limit (the server accepts more).
- To read more, repeat the call increasing `page[offset]` by the limit until `offset` reaches `meta.page.totalCount`.
- Prefer narrowing the request with `filter[...]` parameters over reading everything and filtering afterwards.
- Endpoints that are not paginated (for example `GET /tags`) return everything: use their filters.
- Do not call operations flagged `x-llm-exclude: true`: they create, change or delete data.

## Quick start

The examples below read the server address and the JWT from the environment:

```bash
export BOOKKEEPING_URL=http://localhost:4000
export TOKEN=<your jwt>
```

`curl -g` is needed because of the `[` `]` in filter names. Note that a token in the URL ends up in shell history and server logs.

## Tags

Labels attached to logs (and runs). Each tag may notify an e-mail list and a Mattermost channel.

### `GET /tags` - List tags

Operation id: `listTags`

Returns every tag matching the filters, **not paginated**.
Returns `204 No Content` (empty body) when nothing matches, so clients must not assume a JSON body.

| Parameter | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `filter[ids]` | query | string | no | Comma separated tag ids. |
| `filter[texts]` | query | string | no | Comma separated exact tag texts. Takes precedence over `filter[partialText]`. |
| `filter[partialText]` | query | string | no | Substring match on the tag text. Ignored when `filter[texts]` is given. |
| `filter[emails]` | query | string | no | Comma separated e-mail addresses. |
| `filter[mattermosts]` | query | string | no | Comma separated Mattermost channel names. |

Responses:

- `200`: Matching tags.
- `204`: No tag matches the filters. No body.
- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes/<query|params|body>/<field>`.

```bash
curl -g "$BOOKKEEPING_URL/api/tags?token=$TOKEN"
```

### `POST /tags` - Create a tag (not for LLM use)

Operation id: `createTag`

**Not meant for LLM or MCP tool use** (`x-llm-exclude`).

**Required role:** `admin`

Request body (JSON):

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `text` | string | yes | Words, digits, `-` and `+` only. min length 2, max length 20, pattern `[\w\|\-+]{1,}` |
| `description` | string \| null | no | max length 100 |
| `mattermost` | string \| null | no | |
| `email` | string \| null | no | One or more e-mail addresses, comma separated. |
| `color` | string \| null | no | pattern `^#[0-9A-Fa-f]{6}$` |

Responses:

- `201`: The created tag.
- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes/<query|params|body>/<field>`.
- `403`: The token lacks the role required by this operation.
- `409`: A tag with the same text already exists.

```bash
curl -g -X POST "$BOOKKEEPING_URL/api/tags?token=$TOKEN" \
-H 'content-type: application/json' \
-d '{"text":"FOOD","description":"Food related issues","email":"cake@cern.ch,test@cern.ch","mattermost":"food-channel","color":"#ff8800"}'
```

### `GET /tags/name` - Get a tag by its text

Operation id: `getTagByName`

Looks a tag up by its exact text (case as stored). Use this when you know the label but not the id.

| Parameter | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `name` | query | string | yes | Exact tag text. Must be URL-encoded. |

Responses:

- `200`: The tag.
- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes/<query|params|body>/<field>`.
- `404`: The requested entity does not exist.

```bash
curl -g "$BOOKKEEPING_URL/api/tags/name?name=FOOD&token=$TOKEN"
```

### `GET /tags/{tagId}` - Get a tag by id

Operation id: `getTagById`

| Parameter | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `tagId` | path | integer | yes | Tag identifier. |

Responses:

- `200`: The tag.
- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes/<query|params|body>/<field>`.
- `404`: The requested entity does not exist.

```bash
curl -g "$BOOKKEEPING_URL/api/tags/1?token=$TOKEN"
```

### `PUT /tags/{tagId}` - Update a tag (not for LLM use)

Operation id: `updateTag`

Only `description`, `email`, `mattermost`, `color` and `archivedAt` can change;
the tag `text` is immutable. Set `archivedAt` to a date to archive the tag and to `null` to restore it.

**Not meant for LLM or MCP tool use** (`x-llm-exclude`).

**Required role:** `admin`

| Parameter | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `tagId` | path | integer | yes | Tag identifier. |

Request body (JSON):

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `description` | string \| null | no | max length 100 |
| `mattermost` | string \| null | no | |
| `email` | string \| null | no | One or more e-mail addresses, comma separated. |
| `color` | string \| null | no | pattern `^#[0-9A-Fa-f]{6}$` |
| `archivedAt` | string \| integer \| null (date-time) | no | Date to archive the tag, either an ISO 8601 date-time string or epoch milliseconds, null to restore it. |

Responses:

- `201`: The updated tag. (Status is `201`, not `200`.)
- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes/<query|params|body>/<field>`.
- `403`: The token lacks the role required by this operation.
- `404`: The requested entity does not exist.

```bash
curl -g -X PUT "$BOOKKEEPING_URL/api/tags/1?token=$TOKEN" \
-H 'content-type: application/json' \
-d '{"description":"Updated description","archivedAt":null}'
```

### `DELETE /tags/{tagId}` - Delete a tag (not for LLM use)

Operation id: `deleteTag`

**Not meant for LLM or MCP tool use** (`x-llm-exclude`).

**Required role:** `admin`

| Parameter | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `tagId` | path | integer | yes | Tag identifier. |

Responses:

- `200`: The deleted tag.
- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes/<query|params|body>/<field>`.
- `403`: The token lacks the role required by this operation.
- `404`: The requested entity does not exist.

```bash
curl -g -X DELETE "$BOOKKEEPING_URL/api/tags/1?token=$TOKEN"
```

### `GET /tags/{tagId}/logs` - List logs carrying a tag

Operation id: `listLogsByTag`

| Parameter | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `tagId` | path | integer | yes | Tag identifier. |
| `page[limit]` | query | integer | no | Maximum number of logs to return. LLMs and agents must use 100 or less. |
| `page[offset]` | query | integer | no | |

Responses:

- `200`: A page of logs.
- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes/<query|params|body>/<field>`.
- `404`: The requested entity does not exist.

```bash
curl -g "$BOOKKEEPING_URL/api/tags/1/logs?page[limit]=100&token=$TOKEN"
```
Loading
Loading