Webhooks, rate limits, and project keys

The control-plane knobs that govern how a project is exposed to the outside world. All endpoints in this page are JWT-only — a leaked jg_p_* key must not be able to disable rate limits, redirect webhooks, or mint sibling keys.

Project settings

GET /api/projects/{id}/settings

Read the current settings. The webhook secret is never returned (only its 13-char prefix and a boolean indicating whether it's set).

Auth: Owner JWT.

GET /api/projects/3a7b5c1d-.../settings
Authorization: Bearer eyJhbGc...
{
  "settings": {
    "webhook_url":           "https://api.partner.example/juglans/events",
    "webhook_secret_prefix": "whsec_a1b2c3d",
    "webhook_secret_set":    true,
    "rate_limit_rpm":        120
  }
}

PATCH /api/projects/{id}/settings

Partial update. Fields not present are left alone. Pass empty string to clear webhook_url; pass 0 to clear rate_limit_rpm.

Auth: Owner JWT.

PATCH /api/projects/3a7b5c1d-.../settings
Authorization: Bearer eyJhbGc...
Content-Type: application/json

{
  "webhook_url":     "https://api.partner.example/juglans/events",
  "rate_limit_rpm":  120
}
{
  "settings": {
    "webhook_url":           "https://api.partner.example/juglans/events",
    "webhook_secret_prefix": "whsec_a1b2c3d",
    "webhook_secret_set":    true,
    "rate_limit_rpm":        120
  }
}

webhook_url must start with http:// or https://. Pass 0 or any non-positive rate_limit_rpm to clear the limit — the value is coerced to NULL server-side rather than stored.

POST /api/projects/{id}/settings/webhook/rotate-secret

Generate a fresh whsec_* signing secret. The plaintext is returned once; afterwards only the hash + prefix is retrievable.

Auth: Owner JWT.

curl -X POST https://api.juglans.ai/api/projects/3a7b5c1d-.../settings/webhook/rotate-secret \
  -H "Authorization: Bearer eyJhbGc..."
{
  "settings": {
    "webhook_url":           "https://api.partner.example/juglans/events",
    "webhook_secret_prefix": "whsec_e5f6a7b",
    "webhook_secret_set":    true,
    "rate_limit_rpm":        120
  },
  "secret": "whsec_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6"
}

Use this secret to verify HMAC-SHA-256 signatures on incoming webhook bodies.

Honest status: webhook configuration storage is implemented end-to-end (settings round-trip, secret hashing, rotation). The asynchronous event delivery — actually emitting POSTs to your URL when a chat completes or a tool fires — is on the roadmap. Setting these fields today reserves the slot; events will start flowing once delivery ships. The signing-secret format will not change.

Rate limits

rate_limit_rpm is a fixed-window-per-minute ceiling on POST /api/projects/{id}/chat. The window is per-project (not per-key, not per-end-user). Exceeding the limit returns 429 with a body indicating retry timing:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{
  "error":               "rate limit exceeded",
  "retry_after_seconds": 23,
  "limit_rpm":           120
}

NULL (or 0 on PATCH) means unlimited. There is no per-route configuration — the limit only applies to chat.


Project API keys

Mint, list, and revoke jg_p_* keys. JWT-only by design: keys cannot manage themselves (a leaked key could otherwise rotate itself and lock out the owner).

POST /api/projects/{id}/api-keys

Mint a new key. The plaintext is returned once.

Auth: Owner JWT.

POST /api/projects/3a7b5c1d-.../api-keys
Authorization: Bearer eyJhbGc...
Content-Type: application/json

{ "name": "production backend" }
{
  "api_key": {
    "id":         "9a8b7c6d-...",
    "project_id": "3a7b5c1d-...",
    "name":       "production backend",
    "prefix":     "jg_p_a1b2c3d",
    "created_at": "2026-04-29T10:00:00Z",
    "key":        "jg_p_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1"
  }
}

Surface key to the operator once and never persist it. After this response, only prefix (the first 12 characters) is retrievable.

GET /api/projects/{id}/api-keys

List active (unrevoked) keys.

Auth: Owner JWT.

{
  "api_keys": [
    {
      "id":           "9a8b7c6d-...",
      "name":         "production backend",
      "prefix":       "jg_p_a1b2c3d",
      "created_at":   "2026-04-29T10:00:00Z",
      "last_used_at": "2026-04-29T11:32:14Z"
    }
  ]
}

last_used_at is best-effort touched on every successful authentication.

DELETE /api/projects/{id}/api-keys/{key_id}

Revoke a key. 204 No Content. Effective immediately — subsequent calls bearing the key fail with 401.

Auth: Owner JWT.

curl -X DELETE https://api.juglans.ai/api/projects/3a7b5c1d-.../api-keys/9a8b7c6d-... \
  -H "Authorization: Bearer eyJhbGc..."