# Check-in qua HTTP (curl)

> Báo lượt chạy từ cron shell, CI hoặc ngôn ngữ không có SDK bằng một lệnh curl: bắt đầu, kết thúc, heartbeat.

Mọi thứ gửi được một request HTTPS đều báo được lượt chạy: một dòng cron shell, một bước CI, một script PHP, một chương trình Go không có SDK. Check-in qua HTTP dùng đúng định dạng URL của API crons HTTP của Sentry, nên script sẵn có vẫn chạy với DSN Belvador.

## URL check-in

```text
https://ingest.belvador.dev/api/<project-id>/cron/<monitor-slug>/<public-key>/
```

`<project-id>` và `<public-key>` lấy từ DSN của project (`https://<public-key>@…/<project-id>`). Trang Monitor hiện URL đầy đủ cho từng monitor, đã điền sẵn. Hãy coi nó như DSN: gửi được dữ liệu vào project nhưng không đọc được gì.

## Báo một lượt có lúc bắt đầu và lúc kết thúc

Chọn một id cho mỗi lượt (32 ký tự hex) và gửi kèm cả hai request, để Belvador đo thời lượng và nhận ra lượt đã bắt đầu mà không bao giờ xong.

```bash
ID=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n')
curl -fsS -m 10 --retry 5 -o /dev/null "https://ingest.belvador.dev/api/<project id>/cron/nightly-invoices/<public key>/?status=in_progress&check_in_id=$ID" || true
```

Khi job thành công:

```bash
curl -fsS -m 10 --retry 5 -o /dev/null "https://ingest.belvador.dev/api/<project id>/cron/nightly-invoices/<public key>/?status=ok&check_in_id=$ID" || true
```

Khi job lỗi:

```bash
curl -fsS -m 10 --retry 5 -o /dev/null "https://ingest.belvador.dev/api/<project id>/cron/nightly-invoices/<public key>/?status=error&check_in_id=$ID" || true
```

Ví dụ shell báo cả hai trường hợp:

```bash
if ./nightly-invoices.sh; then STATUS=ok; else STATUS=error; fi
curl -fsS "$URL?status=$STATUS&check_in_id=$ID"
```

## Hoặc: một nhịp heartbeat

Khi việc không báo được lúc bắt đầu — vòng kiểm sức khoẻ, worker gửi tín hiệu mỗi vài phút — gửi một `ok` khi xong. Dùng monitor **Theo chu kỳ** cho trường hợp này.

```bash
curl -fsS -m 10 --retry 5 -o /dev/null "https://ingest.belvador.dev/api/<project id>/cron/nightly-invoices/<public key>/?status=ok" || true
```

## Tham số

| Tham số | Giá trị |
|---|---|
| `status` | `in_progress`, `ok`, `error` (bắt buộc) |
| `check_in_id` | 32 ký tự hex; cùng một id ở lúc bắt đầu và lúc kết thúc của một lượt |
| `environment` | ví dụ `production` (mặc định) hoặc `staging` |
| `duration` | giây, nếu bạn tự đo |
| `release` | phiên bản app của bạn |
| `outcome` + `reason` | đi với `status=ok`: `skipped` (không có việc, khoẻ) hoặc `unknown` (đã kết thúc, chưa rõ hiệu lực — **Cần xem lại**); `reason` là mã ngắn như `nothing_due` |
| `run_id`, `attempt`, `will_retry` | gộp các lần thử thành một lượt: cùng `run_id` ở mọi lần thử, `attempt` đếm từ 1, `will_retry=true` ở lần thử lỗi mà hàng đợi sẽ chạy lại |

Lần báo kết thúc không có `check_in_id` sẽ đóng lần bắt đầu mới nhất còn mở của monitor đó.

Request `POST` với body JSON cũng mang được `monitor_config` — cùng các thiết lập như tuỳ chọn của `withMonitor`, theo tên của Sentry (`schedule`, `timezone`, `checkin_margin`, `max_runtime`, `failure_issue_threshold`, `recovery_threshold`) — để tạo hoặc cập nhật monitor ngay từ script.

## Phản hồi

| Mã | Ý nghĩa |
|---|---|
| `202` kèm `{"id": "…"}` | Đã nhận; `id` là id của check-in |
| `400` | Slug, trạng thái hoặc body không hợp lệ |
| `401` | Khoá không tồn tại, đã tắt, hoặc thuộc project khác |
| `403` | Workspace hiện không nhận dữ liệu |
| `413` | Body quá 100 KiB |
| `429` | Quá nhiều check-in: tối đa 60 mỗi monitor và môi trường mỗi phút, cộng giới hạn theo tháng của gói. Hãy tôn trọng `Retry-After`. |

## Quy tắc

- Không gọi URL này từ trang web: check-in từ trình duyệt bị từ chối, và khoá sẽ lộ cho mọi người xem trang.
- `curl -fsS` thất bại im lặng khi có lỗi, nên Belvador gặp sự cố cũng không làm job của bạn lỗi. Đừng để job phụ thuộc vào việc check-in thành công.
- Nếu check-in bị từ chối vì giới hạn tốc độ, monitor ghi **Không quan sát được** chứ không phải **Bị lỡ**.
