Graine AI

Replace an agent's event subscriptions

Replace one agent's whole webhook set in a single call.

Replace one agent's whole webhook set in a single call.

It exists so an integration written against another platform's whole-set shape ports across unchanged:

{"operation": "edit_event_subscriptions",
 "event_subscriptions": [
    {"event_type": ["call_completed"],
     "callback_url": "https://example.com/hooks",
     "headers": {"Authorization": "Bearer ..."},
     "method_type": "POST"}]}

It REPLACES, it does not merge. Whatever this agent has that the request does not list is deleted. Send the complete set every time; send [] to unsubscribe the agent entirely. event_subscriptions: null is refused rather than treated as empty, so a caller that lost its list to a bad parse cannot silently wipe a live configuration.

Duplicate event types collapse, last one wins, so listing an event twice reconfigures it and never doubles its delivery. Entries that share a callback URL and method collapse into ONE subscription carrying all their event types, and later entries win on the shared attributes (headers, secret, enabled).

Every callback URL is validated BEFORE anything is written, so a bad URL is a 400 and no partial replace happens. dropped_event_types reports anything requested that is not in the stored result; it should always be empty, and it is returned so you never have to assume.

event_subscriptions in the response is the whole stored set — header values and secrets masked, as everywhere else on this API.

The API runs several processes, each with its own 60-second subscription cache. A change is live immediately for the process that served this request and within 60 seconds everywhere else.

Errors: 400 (bad operation, event type, method, header or URL), 401, 403, 404 (the agent is not one of yours), 422, 429, 503.

Rate limit: 1000 requests per minute per organization (bucket default). Exceeding it returns 429 with Retry-After; the X-RateLimit-* response headers report your remaining allowance on every call.

PATCH
/v2/agents/{agent_id}/event-subscriptions
/v2/agents/6c6f4cc3-f56c-479d-8422-7f76694daa29/event-subscriptions

The Authorization access token

Authorization

Authorization
Required
Bearer <token>

Your Graine API key. Create one in the dashboard under Developers, or via POST /v2/api-keys. Send it as Authorization: Bearer <key>.

In: header

Request Body

application/jsonRequired

operationOperation

Must be 'edit_event_subscriptions'.

Default: "edit_event_subscriptions"

event_subscriptions
Required
Event Subscriptions

The COMPLETE set for this agent. Anything not listed is removed. Send [] to unsubscribe the agent entirely.

Path Parameters

agent_id
Required
Agent Id

The agent whose subscription set is being replaced.

Query Parameters

organization_idOrganization Id

Must match the organisation the API key belongs to.

Response Body

200

The complete set of subscriptions that now exist for this agent.

agent_idAgent Id | null

The agent whose set was replaced.

operation
Required
Operation

Echo of the requested operation.

event_subscriptions
Required
Event Subscriptions

The subscriptions that now exist for this agent — the whole set.

dropped_event_types
Required
Dropped Event Types

Event types present in the request that are NOT in the stored set. Normally empty: every entry is validated before anything is written.

400

1000 — malformed input the schema could not reject: an unknown event type, a method other than POST/PUT, a reserved header name, or a callback_url pointing at a private, loopback or metadata address.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

401

1100 — missing, unknown or inactive API key. 1101 — a browser session token was presented instead of a gat_ key.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

403

1102 — the key is valid but lacks the scope this endpoint requires, or names another organisation. GET /v2/scopes reports which scopes the key holds.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

404

1200 — no such subscription or delivery in this organisation. Cross-tenant reads answer 404, never 403.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

422

1001 — the body or query failed validation; the message names the first offending field.

error
Required
integer

Stable integer code from the error table. Branch on this, not on the message.

message
Required
string

One human-readable sentence. Wording may change; the code will not.

429

1300 — per-organisation rate limit exceeded. Carries Retry-After.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

503

1501 — the webhook subscription store is unreachable.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

504

1502 — an upstream did not respond in time.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

curl -X PATCH "https://api.graine.ai/v2/agents/6c6f4cc3-f56c-479d-8422-7f76694daa29/event-subscriptions?organization_id=string" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "edit_event_subscriptions",
    "event_subscriptions": [
      {
        "event_type": [
          "call_completed",
          "recording_completed"
        ],
        "callback_url": "https://example.com/hooks/graine",
        "headers": {
          "X-Tenant": "acme"
        },
        "method_type": "POST",
        "secret": "whsec_exampleSigningSecretDoNotUse",
        "enabled": true
      }
    ]
  }'

The complete set of subscriptions that now exist for this agent.

{
  "agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
  "operation": "edit_event_subscriptions",
  "event_subscriptions": [
    {
      "subscription_id": "b91d2f77-3ea5-4c58-9d10-6f8b4a2c7e31",
      "agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
      "scope": "agent",
      "event_types": [
        "all_processing_completed"
      ],
      "callback_url": "https://example.com/hooks/graine",
      "method_type": "POST",
      "headers": {
        "Authorization": "***"
      },
      "header_names": [
        "X-Tenant"
      ],
      "enabled": true,
      "has_secret": true,
      "secret": "***",
      "secret_unavailable": false,
      "created_at": "2026-08-20T13:06:01.724Z",
      "updated_at": "2026-08-20T13:27:49.073Z",
      "created_by": "dev_1a2b3c4d5e6f7a8b"
    }
  ],
  "dropped_event_types": []
}