# Instance API

## Register Instance

`POST /api/v1/instances/register`

```json
{
  "licenseCode": "AUTH-XXXX-XXXX",
  "instanceId": "550e8400-e29b-41d4-a716-446655440000",
  "instanceName": "production-01",
  "nodeRole": "master",
  "systemName": "linux",
  "systemArch": "amd64",
  "backendUrl": "https://finance.example.com"
}
```

```json
{
  "valid": true,
  "type": "commercial",
  "version": "1.0.0",
  "instanceToken": "inst_xxx",
  "expiresAt": "2026-07-19T02:00:00Z"
}
```

Initial registration creates one binding for the globally unique `instanceId`. Authorized re-registration updates that binding, revokes earlier tokens immediately, and returns a new token. Tokens use 32 random bytes and only their SHA-256 hashes are stored. Token expiry is capped by license expiry. A successful active or suspended heartbeat rolls an unexpired token forward by `INSTANCE_TOKEN_TTL`; revoked and already expired tokens are never extended.

An active existing instance can be re-registered only when its current token is supplied as `Authorization: Bearer inst_xxx`. An instance ID cannot be reused by another license. After an administrator unbinds or resets an instance, the same license may register it again without the old token.

`backendUrl` must use HTTP or HTTPS. IP addresses require an explicit port. User information, query strings, and fragments are rejected.

## Heartbeat

`POST /api/v1/instances/heartbeat`

```http
Authorization: Bearer inst_xxx
Content-Type: application/json
```

```json
{
  "instanceId": "550e8400-e29b-41d4-a716-446655440000",
  "reportedVersion": "1.0.0",
  "ip": "10.0.0.1",
  "backendUrl": "https://finance.example.com",
  "nonce": "client_random_123456"
}
```

```json
{
  "status": "active",
  "validUntil": "2026-07-12T03:00:00Z",
  "expiresAt": "2027-01-01T00:00:00Z",
  "licenseNo": "LIC-XXXXXXXX",
  "instanceId": "550e8400-e29b-41d4-a716-446655440000",
  "sequence": 104,
  "keyId": "license-2026-01",
  "features": ["http", "worker", "plugins"],
  "nonce": "client_random_123456",
  "issuedAt": 1783821900,
  "signature": "SIGN-..."
}
```

Active, suspended, and revoked licenses return HTTP 200 with a signed status payload while the instance token remains valid. Malformed, expired, rotated, revoked, or mismatched tokens return HTTP 401 with `INSTANCE_TOKEN_INVALID`.

Each successful heartbeat locks the instance binding, increments its sequence, and updates the reported version, reported IP, source IP, last-seen time, and token last-used time in one transaction.

`backendUrl` is required on every heartbeat. Its normalized hostname must match the hostname registered for the instance. Scheme, port, and path do not affect this comparison. A mismatch returns `403 DOMAIN_MISMATCH` and does not update the binding, token, or sequence.

Query and Heartbeat share this same signed BIGINT sequence and never maintain separate counters. Valid values are `1..9223372036854775807`.

## Error Codes

- `400 LICENSE_INVALID`
- `401 INSTANCE_TOKEN_INVALID`
- `403 LICENSE_SUSPENDED`
- `403 LICENSE_REVOKED`
- `403 DOMAIN_MISMATCH`
- `409 INSTANCE_LIMIT_EXCEEDED`
- `409 INSTANCE_BINDING_CONFLICT`
- `429 RATE_LIMITED`
- `500 INTERNAL_ERROR`
- `503 LICENSE_SERVICE_UNAVAILABLE`
