> For the complete documentation index, see [llms.txt](https://docs.mymedtrics.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.mymedtrics.com/get-started/conventions.md).

# Conventions

Headers, idempotency, webhooks, rate limits and hosting — the rules every call follows.

Read from the gateway and webhook worker source at the snapshot commit; every claim cites its file. These paragraphs are the client-facing text for the Conventions section until they are moved into `openapi.yaml` descriptions.

### Hosting and base URL

Every tenant is served from its own host: `https://<client-name>.mymedtrics.com` (decided 2026-10-10; previously the spec carried the placeholder `api.medtrics.example`). The public ingress (`docker/caddy/Caddyfile`) routes on that one origin:

| Path prefix                                                                     | Routes to                                                                                                           |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `/connect/*`                                                                    | Connect API gateway (`handle_path` strips `/connect`, rewrites to `/functions/v1/connect-api-gateway/connect{uri}`) |
| `/api-banner/*`, `/api/*`                                                       | Medtrics Classic SIS Bridge                                                                                         |
| `/lti/jwks`, `/lti/oidc/login`, `/lti/oidc/callback`                            | LTI 1.3 functions                                                                                                   |
| `/auth/*`, `/rest/*`, `/graphql/*`, `/realtime/*`, `/storage/*`, `/functions/*` | Supabase services (internal REST surface)                                                                           |
| everything else                                                                 | the Oracle web app                                                                                                  |

So a client calls `https://<client-name>.mymedtrics.com/connect/programs`; preview environments use `https://pr-<n>.dev.medtrics.xyz` with the same paths. Local e2e tests bypass Caddy (`http://localhost:54321/functions/v1/connect-api-gateway`). The sandbox host for client onboarding is still an open item.

### Idempotency

Send an `Idempotency-Key` header on `POST /connect/webhooks` (and on `POST /connect/import-jobs` once it ships). Any opaque string works; the gateway stores a SHA-256 hash of it with the created resource, scoped to your API app, and never stores the key itself. Repeating the same request with the same key returns the original resource with **HTTP 200** instead of 201 and does not create a duplicate. On replay the response never includes the webhook signing secret: the key is a deduplication token, not a way to recover a secret. Keys have no expiry in the current implementation; use a new key for each distinct subscription you intend to create.

### Webhook delivery

Every delivery is an HTTPS `POST` to your subscription's `target_url` with a JSON envelope:

```json
{
  "id": "evt_…",
  "type": "learner.updated",
  "created_at": "2026-07-01T13:00:00Z",
  "resource": { "type": "learner", "id": "learner_01J9XZ3K7Q8W" },
  "data": { }
}
```

`data` is the redacted event payload for the event type. Headers on every delivery:

| Header                         | Value                                       |
| ------------------------------ | ------------------------------------------- |
| `Medtrics-Webhook-ID`          | event id (`evt_…`)                          |
| `Medtrics-Webhook-Delivery-ID` | delivery id, unique per attempt series      |
| `Medtrics-Webhook-Event`       | event type, one of the five in the contract |
| `Medtrics-Webhook-Timestamp`   | Unix seconds when the delivery was signed   |
| `Medtrics-Webhook-Attempt`     | 1-based attempt number                      |
| `Medtrics-Webhook-Signature`   | `v1=<hex HMAC-SHA256>`                      |
| `User-Agent`                   | `Medtrics-Connect-Webhooks/1.0`             |

### Verifying a signature

Compute HMAC-SHA256 with your subscription's signing secret (shown once at creation, prefix `whsec_`) over the string `"<timestamp>.<raw body>"`, hex-encode it, and compare in constant time with the value after `v1=`. Reject deliveries whose timestamp is further from now than your subscription's `timestamp_tolerance_seconds`.

```bash
expected=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$WHSEC" | awk '{print $2}')
[ "v1=$expected" = "$SIG" ] && echo verified
```

### Retries

A delivery is retried when the request fails at the network level, returns no status, returns any 5xx, or returns 408, 409, 425 or 429. Any other response is final. Up to **5 attempts** are made with delays of **1 min, 5 min, 15 min, 1 h, 3 h** after the first failure; after the last failure the delivery is marked `failed`. Delivery status (`pending`, `sent`, `retrying`, `failed`, `disabled`) and attempt counts are visible on the subscription's delivery history in the contract. Respond with any 2xx quickly and do the work asynchronously.

### Rate limits

The gateway returns `Medtrics-RateLimit-Limit`, `Medtrics-RateLimit-Remaining` and `Medtrics-RateLimit-Reset` on every success and `429` with `Retry-After` when exceeded. The default limit published to the portal is **60 requests per minute** per API app; per-client overrides can only lower it.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.mymedtrics.com/get-started/conventions.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
