Graine AI

Update an agent

Partially update an agent.

Partially update an agent. Only what you send is touched.

Send the same shape you read back. To change one control, send one control:

{"configuration": {"session_policy": {"session_ttl": 600}}}

That leaves the other eleven session controls, the other six sections and everything outside configuration exactly as they were. Nothing you omit is nulled — omission is not an instruction.

Replace or merge, per section

  • Merged, key by key, at every depth — configuration and each of its sections (runtime and its four blocks, and the five policies), prompts, variables, analytics, integrations, settings, follow_up_config, conversation_config. Send one key inside one of these and only that key changes.
  • Replaced whole — every list. labels, configuration.post_processing, configuration.external_functions, variables.custom and analytics.custom take the list you send in place of the one stored, so send every entry you want to keep.
  • Replaced whole, with blanks backfilled — multilingual. Sending it replaces the stored block rather than merging into it, but any field you leave blank on a language you already had is filled back in from what is stored, so a partial save cannot silently strip a voice or a per-language prompt. Send null to go back to a single language.
  • Scalars — display_name, summary, greeting_message, status, category are set to what you send.

Languages. multilingual.language_switch_trigger decides when an agent with more than one language changes language on a call:

  • "requested_or_auto_detected" — what an agent without the setting does. It switches when the caller says a full sentence in another of its languages, or asks for one.
  • "caller_requested" — it switches only when the caller asks: by naming the language, with a question such as "can you speak Tamil?", or by answering the agent's own question about which language to use.

Any other value is rejected with 422. Send it inside the multilingual block you read back, with its other keys; a block that names neither enabled nor languages — {"multilingual": {"language_switch_trigger": "caller_requested"}} — changes only the settings it names. A language's handoff_message is spoken in that language's voice when the call switches into it, and may use {agent_name} and {language}.

To start one call in another of the agent's languages, send agent_language among the call's variables (in a batch upload, a column of that name) set to one of the agent's language codes, such as "ta"; case does not matter. Left out or empty, the call opens in multilingual.active_language. A code the agent doesn't have is refused where you send it: POST /v2/calls answers 400 with the agent's languages, and an uploaded list leaves that row out — listed, with why, in skipped_rows — and dials the rest. Anywhere else a call starts, such as an inbound call, it opens in multilingual.active_language. On an agent with one language, agent_language is an ordinary variable.

Explicit removals. knowledge_base: null detaches the knowledge base. integrations.mcp: null disconnects connected tools. analytics.custom: null or [] stops per-call extraction. configuration.external_functions: [] removes every function.

An empty body is rejected with 400 (code 1000) before the request leaves Graine. An unrecognised field at the top level of the body is rejected with 422 (code 1001) naming the field, rather than being accepted and ignored. Below the top level the objects accept keys this reference does not name — a provider's own options live there — so a misspelled key at that depth is not caught; calling_guard is the exception and is checked field by field. Read the agent back to confirm a change landed.

An ACTIVE agent cannot be edited. Switch it off first by sending {"status": "INACTIVE"} on its own — the one change an ACTIVE agent accepts; any other body, including that status with anything beside it, returns 409 (code 1201).

Approvals. With a key a workspace MEMBER minted, in a workspace that requires approval: a change to an ACTIVE agent, or status: "ACTIVE", answers 202 — the change waits as an approval request and is applied, exactly as sent, when an Admin approves it (the live agent is unchanged until then). Other fields sent with status: "ACTIVE" to an agent that is not live yet are saved straight away. Admin-minted and older keys never get 202.

A passing test run first. Where the workspace requires a passing test run before an agent goes live (Workspace settings), status: "ACTIVE" on an agent that is not live yet is refused with 409 (code 1201) unless the agent's latest test run passed: it has none, the latest run did not pass, or it is still running. The message says which. Nothing in the request is saved, and no approval request is opened. An agent that is already live, and a request that does not make an agent live, are not affected.

Rate limit: 2000 requests per minute per organization, counted together with every endpoint that has no stricter limit of its own. Exceeding it returns 429 with Retry-After; the X-RateLimit-* response headers report your remaining allowance on every call.

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

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

display_nameDisplay Name | null

The agent's name. Letters, digits, spaces and - _ . ( ) only.

Minimum length: 1Maximum length: 100

summarySummary | null

Your own note about what this agent is for.

Maximum length: 2000

greeting_messageGreeting Message | null

The first line the agent speaks. May contain {variables}.

Maximum length: 20000

statusStatus | null

DRAFT | ACTIVE | INACTIVE | ARCHIVED. An ACTIVE agent accepts one change only: this field set to INACTIVE, sent on its own.

categoryCategory | null

What kind of work this agent does. Any text is stored; only conversation, extraction, summarization and custom can be used to filter GET /v2/agents.

labelsLabels | null

Replaces the whole list of labels.

promptsPrompts

The agent's instructions.

agent_kindAgent Kind | null

prompt or flow. To make a flow agent, send flow with agent_kind flow -- on create, or to turn a prompt agent into one. To turn a flow agent back into a prompt agent, send agent_kind prompt with prompts.conversation.system.

flowFlow | null

A flow agent's flow, replaced whole, in the format described under Flow agents (Flow API, with complete examples). Checked before anything is saved: a flow with a problem is refused with 1001 and a message naming it.

variablesVariables

Prompt placeholders.

analyticsAnalytics

Per-call extraction.

integrationsIntegrations | null

webhook.url sets the status callback; webcall enables browser calling; mcp connects tools. Send mcp: null to disconnect them.

knowledge_baseKnowledge Base

The name to show as this agent's knowledge base. Setting it binds nothing — the agent searches what configuration.runtime.language_model.knowledge_base_config names. Send null to clear the name AND detach that binding.

calling_guardCalling Guard

The hours this agent may ring people. Applies to POST /v2/calls only; a call outside the window is rescheduled INTO the next open window — spread across it by default, see spread — not rejected. Send {"enabled": false} to remove it.

conversation_configConversation Config | null

The flat view of the ten most-changed controls. Writing one here is the same as writing it in its policy section.

configurationConfiguration

How this agent runs a call.

settingsSettings

Agent-wide switches.

follow_up_configFollow Up Config

Follow-up behaviour.

multilingualMultilingual | null

Per-language voices and prompts. Sending this REPLACES the stored block; send null to go back to a single language.

Path Parameters

agent_id
Required
Agent Id

Agent identifier.

Query Parameters

organization_idOrganization Id

Must match the organisation the API key belongs to.

Response Body

200

The updated agent, at its new version.

agent_id
Required
Agent Id

Stable identifier for this agent. Use it everywhere else in the API.

org_idOrg Id | null

The organisation this agent belongs to. Always the organisation your API key belongs to.

display_nameDisplay Name | null

The agent's name, as it appears in the dashboard.

summarySummary | null

A short note about what this agent is for. Yours to write; never shown to a caller.

greeting_messageGreeting Message | null

The first line the agent speaks. May contain {variables}.

statusStatus | null

DRAFT | ACTIVE | INACTIVE | ARCHIVED. ACTIVE locks the agent: the only change it accepts is {"status": "INACTIVE"} sent on its own, and it must be INACTIVE before it can be edited or archived. The status does not decide whether the agent can place calls.

categoryCategory | null

What kind of work this agent does. Yours to choose, and it changes nothing at call time. Any text is stored, but only conversation, extraction, summarization and custom can be used to filter GET /v2/agents.

agent_kindAgent Kind | null

prompt: the agent follows one set of instructions (prompts). flow: the conversation is a flow of steps and the paths between them (flow) -- see Flow agents.

versionVersion

Incremented on every successful update. Read it back after a write to confirm the change landed.

Default: 1

timestampsTimestamps

When this agent was created and last changed.

labelsLabels

Free-form labels for your own filtering.

@minItems 0

promptsPrompts

The agent's instructions. Empty on a list row — prompts are not fetched while listing.

flowFlow | null

A flow agent's flow: its steps, what each one says or does, and the paths between them, in the format described under Flow agents (Flow API, with complete examples). Null for a prompt agent, and on a list row -- read the agent on its own.

variablesVariables

Placeholders the greeting and prompts may use.

analyticsAnalytics

What is extracted from every call, beyond the summary.

integrationsIntegrations

Where this agent is wired up: a status callback under webhook, browser calling under webcall, connected tools under mcp. Only what is configured appears, and a list row never carries mcp — connected tools are not read while listing.

knowledge_baseKnowledge Base

The name of the knowledge base shown against this agent. A label: what the agent actually searches is configuration.runtime.language_model.knowledge_base_config. Null when none is set, and null on a list row — it is not fetched while listing.

conversation_configConversation Config | null

A flat view of the ten most-changed controls from the policy sections below — the same values, not a second set. Change either.

configurationConfiguration

How this agent runs a call.

settingsSettings

Agent-wide switches.

follow_up_configFollow Up Config

What happens after a call, when a follow-up is warranted.

multilingualMultilingual | null

Per-language voices and prompts, when the agent switches language mid-call. Null when it speaks one language.

calling_guardCalling Guard | null

The hours this agent may ring people, or null when it may call at any time. Applies to POST /v2/calls only — a call outside the window is rescheduled INTO the next open window, not refused. spread says where in that window it lands: "window" scatters held calls across it (the default), "asap" rings them all in the opening minute, and a number spreads them across that many minutes. Null on a list row — it is not read while listing.

approved_byApproved By | null

The workspace Admin who approved this agent's live configuration — its going live, or the last change applied while live: {member_id, role, at, request_id, number}. Null on an agent nobody approved (approvals were off, or an Admin acted directly).

approved_atApproved At | null

ISO-8601 UTC: when that approval was applied.

dropped_languagesDropped Languages | null

Returned after an update that sent a language which could not be saved — usually a missing voice. Those languages were not stored.

@minItems 0

202

Only for an API key a workspace MEMBER minted, when the workspace requires an Admin's approval for this action. Nothing has changed yet: the action is recorded as an approval request and happens, exactly as sent, when any Admin approves it. Admin-minted and older keys never get this answer. The resource this call created or touched is included under its own name (agent, campaign or batch).

statusStatus

Always pending_approval.

Default: "pending_approval"

codeCode

Always approval_required.

Default: "approval_required"

detail
Required
Detail

What happens next, in a sentence.

approval_request_id
Required
Approval Request Id

The approval request's id.

approval_request
Required
Approval Request

The request this call opened or added to.

400

1000 — malformed or contradictory input. 1002 — a required parameter is missing.

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 an API 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 organization. GET /v2/scopes reports what a 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 resource in your organization. A resource belonging to another organization answers 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.

409

1201 — the resource's current state does not allow this action.

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 — the per-organization request rate or concurrent-call limit was reached. 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 — a service this endpoint depends on is unreachable or returned a 5xx.

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 — a service this endpoint depends on did not answer 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?organization_id=string" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Support Bot",
    "summary": "Handles first-line support for the mobile app.",
    "greeting_message": "Hi, am I speaking with {callee_name}?",
    "status": "ACTIVE",
    "category": "conversation",
    "labels": [
      "support"
    ],
    "prompts": {
      "conversation": {
        "system": "You are Priya, a warm caller from ..."
      },
      "summarization": {
        "system": "You are Priya, a warm caller from ..."
      },
      "extraction": {
        "system": "You are Priya, a warm caller from ..."
      }
    },
    "variables": {
      "custom": [
        "callee_name"
      ]
    },
    "analytics": {
      "custom": [
        {
          "name": "Lead qualification",
          "prompt": "Read the transcript and fill in each field.",
          "required_keys": [
            "intent",
            "callback_time"
          ]
        }
      ]
    },
    "integrations": {
      "webhook": {
        "url": "https://example.com/hooks/graine"
      },
      "webcall": {
        "enabled": true
      }
    },
    "knowledge_base": "product-handbook",
    "calling_guard": {
      "enabled": true,
      "timezone": "Asia/Kolkata",
      "start": "09:00",
      "end": "21:00",
      "days": [
        0,
        1,
        2,
        3,
        4
      ],
      "spread": "window"
    },
    "conversation_config": {
      "finale_message": "Thank you for your time.",
      "presence_probe_message": "Are you still on the call?",
      "probe_interval_seconds": 7,
      "probe_window_seconds": 30,
      "probe_min_words": 7,
      "block_unlisted_callers": false
    },
    "configuration": {
      "runtime": {
        "call_transport": {
          "provider": "default",
          "audio_format": "wav"
        },
        "speech_recognition": {
          "provider": "deepgram",
          "model": "nova-3",
          "language": "hi",
          "stream": true,
          "endpointing": 190,
          "encoding": "linear16",
          "sampling_rate": 16000,
          "task": "transcribe",
          "keywords": "haan,ji,bolo,pearl,sparsh",
          "hinglish_mode": false
        },
        "voice_synthesis": {
          "provider": "cartesia",
          "voice_id": "148a02f1-d396-4615-8552-52483eaf9d9a",
          "voice_name": "Laxmi",
          "speed": 1,
          "speaking_rate": 1,
          "audio_format": "wav",
          "buffer_size": 500,
          "stream": true,
          "caching": true,
          "model": "sonic-3.5"
        },
        "language_model": {
          "provider": "azure",
          "model": "azure/gpt-4o-mini-2",
          "config": {
            "max_tokens": 300,
            "temperature": 0.25
          },
          "knowledge_base_config": {
            "enabled": true,
            "collections": [
              "internal_organization-live-EXAMPLE__product-handbook"
            ],
            "similarity_top_k": 5,
            "max_results": 3,
            "reranker_enabled": true
          }
        }
      },
      "session_policy": {
        "session_ttl": 300,
        "close_after_reply": true,
        "finale_interruptible": false,
        "completion_probe": true,
        "probe_interval_seconds": 7,
        "auto_retry_plan": true,
        "trusted_callers": [
          "+917971442184"
        ],
        "silence_close_after": 12,
        "block_unlisted_callers": false,
        "entry_quota": -1,
        "probe_window_seconds": 30,
        "probe_min_words": 7,
        "probe_start_delay_seconds": 2.5
      },
      "stream_policy": {
        "bargein_word_threshold": 3,
        "force_chunking": true,
        "rapid_path_mode": true,
        "transcript_fidelity_mode": false,
        "emit_step_ms": 200,
        "bargein_backoff_ms": 0,
        "interrupt_on_speech_start": true,
        "ignore_speech_before_greeting": false,
        "greeting_delay_ms": 0,
        "inapp_audio_hz": 24000
      },
      "engagement_policy": {
        "noise_guard_level": 2,
        "scene_bed_gain": -11,
        "listener_cues_enabled": false,
        "presence_probe_enabled": true,
        "keypad_signal_gate": false,
        "presence_probe_after_seconds": 6,
        "listener_cue_gap_seconds": 5,
        "locale_probe_turns": 2,
        "scene_bed_enabled": true,
        "scene_profile_id": "https://ambientsoundgraineai.s3.ap-south-1.amazonaws.com/office-ambience.mp3",
        "cadence_padding": false,
        "listener_cue_start_delay_seconds": 5,
        "presence_probe_message": "Are you still on the line?",
        "suggested_replies": false
      },
      "message_policy": {
        "completion_probe_prompt": "You are judging whether a conversation is finished. It is finished when the customer\'s question has been answered and neither side has anything left to add.",
        "finale_message": "Thank you for your time."
      },
      "compliance_policy": {
        "pii_guard_enabled": false
      },
      "post_processing": [
        {
          "type": "summary",
          "enabled": true
        }
      ],
      "external_functions": [
        {
          "name": "check_order_status",
          "description": "Look up an order by its id.",
          "parameters": {
            "type": "object",
            "properties": {
              "order_id": {
                "type": "string"
              }
            }
          }
        }
      ]
    },
    "settings": {
      "agent_memory": false
    },
    "follow_up_config": {
      "follow_up_agent_id": "b2e9c7a4-5d31-4f68-9c02-7a1b3e5d9f84",
      "follow_up_prompt": "Read the finished call. If the customer asked to be called back, greet them by name and confirm the slot they asked for.",
      "follow_up_extra_logic": {
        "skip_if_ordered": true
      }
    },
    "multilingual": {
      "enabled": true,
      "active_language": "hi",
      "languages": {
        "hi": {
          "agent_name": "Priya"
        },
        "en": {
          "agent_name": "Priya"
        }
      }
    }
  }'

The updated agent, at its new version.

{
  "agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
  "org_id": "string",
  "display_name": "Priya Plumber Activation Agent",
  "summary": "Activates plumbers who signed up but never scanned a code.",
  "greeting_message": "Hello! Am I speaking with {callee_name}?",
  "status": "DRAFT",
  "category": "conversation",
  "agent_kind": "prompt",
  "version": 1,
  "timestamps": {
    "created_at": "2026-08-20T13:06:01.724Z",
    "updated_at": "2026-08-20T13:27:49.073Z"
  },
  "labels": [
    "activation",
    "hindi"
  ],
  "prompts": {
    "conversation": {
      "system": "You are Priya, a warm caller from ..."
    },
    "summarization": {
      "system": "You are Priya, a warm caller from ..."
    },
    "extraction": {
      "system": "You are Priya, a warm caller from ..."
    }
  },
  "flow": {
    "property1": null,
    "property2": null
  },
  "variables": {
    "custom": [
      "callee_name",
      "mobile_number"
    ],
    "placeholders": [
      "callee_name",
      "mobile_number"
    ]
  },
  "analytics": {
    "custom": [
      {
        "name": "Lead qualification",
        "prompt": "Read the transcript and fill in each field.",
        "required_keys": [
          "intent",
          "callback_time"
        ]
      }
    ]
  },
  "integrations": {
    "webhook": {
      "url": "https://example.com/hooks/graine"
    },
    "webcall": {
      "enabled": true
    }
  },
  "knowledge_base": "product-handbook",
  "conversation_config": {
    "finale_message": "Thank you for your time.",
    "presence_probe_message": "Are you still on the call?",
    "probe_interval_seconds": 7,
    "probe_window_seconds": 30,
    "probe_min_words": 7,
    "block_unlisted_callers": false
  },
  "configuration": {
    "runtime": {
      "call_transport": {
        "provider": "default",
        "audio_format": "wav"
      },
      "speech_recognition": {
        "provider": "deepgram",
        "model": "nova-3",
        "language": "hi",
        "stream": true,
        "endpointing": 190,
        "encoding": "linear16",
        "sampling_rate": 16000,
        "task": "transcribe",
        "keywords": "haan,ji,bolo,pearl,sparsh",
        "hinglish_mode": false
      },
      "voice_synthesis": {
        "provider": "cartesia",
        "voice_id": "148a02f1-d396-4615-8552-52483eaf9d9a",
        "voice_name": "Laxmi",
        "speed": 1,
        "speaking_rate": 1,
        "audio_format": "wav",
        "buffer_size": 500,
        "stream": true,
        "caching": true,
        "model": "sonic-3.5"
      },
      "language_model": {
        "provider": "azure",
        "model": "azure/gpt-4o-mini-2",
        "config": {
          "max_tokens": 300,
          "temperature": 0.25
        },
        "knowledge_base_config": {
          "enabled": true,
          "collections": [
            "internal_organization-live-EXAMPLE__product-handbook"
          ],
          "similarity_top_k": 5,
          "max_results": 3,
          "reranker_enabled": true
        }
      }
    },
    "session_policy": {
      "session_ttl": 300,
      "close_after_reply": true,
      "finale_interruptible": false,
      "completion_probe": true,
      "probe_interval_seconds": 7,
      "auto_retry_plan": true,
      "trusted_callers": [
        "+917971442184"
      ],
      "silence_close_after": 12,
      "block_unlisted_callers": false,
      "entry_quota": -1,
      "probe_window_seconds": 30,
      "probe_min_words": 7,
      "probe_start_delay_seconds": 2.5
    },
    "stream_policy": {
      "bargein_word_threshold": 3,
      "force_chunking": true,
      "rapid_path_mode": true,
      "transcript_fidelity_mode": false,
      "emit_step_ms": 200,
      "bargein_backoff_ms": 0,
      "interrupt_on_speech_start": true,
      "ignore_speech_before_greeting": false,
      "greeting_delay_ms": 0,
      "inapp_audio_hz": 24000
    },
    "engagement_policy": {
      "noise_guard_level": 2,
      "scene_bed_gain": -11,
      "listener_cues_enabled": false,
      "presence_probe_enabled": true,
      "keypad_signal_gate": false,
      "presence_probe_after_seconds": 6,
      "listener_cue_gap_seconds": 5,
      "locale_probe_turns": 2,
      "scene_bed_enabled": true,
      "scene_profile_id": "https://ambientsoundgraineai.s3.ap-south-1.amazonaws.com/office-ambience.mp3",
      "cadence_padding": false,
      "listener_cue_start_delay_seconds": 5,
      "presence_probe_message": "Are you still on the line?",
      "suggested_replies": false
    },
    "message_policy": {
      "completion_probe_prompt": "You are judging whether a conversation is finished. It is finished when the customer's question has been answered and neither side has anything left to add.",
      "finale_message": "Thank you for your time."
    },
    "compliance_policy": {
      "pii_guard_enabled": false
    },
    "post_processing": [
      {
        "type": "summary",
        "enabled": true
      }
    ],
    "external_functions": [
      {
        "name": "check_order_status",
        "description": "Look up an order by its id.",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string"
            }
          }
        }
      }
    ]
  },
  "settings": {
    "agent_memory": false
  },
  "follow_up_config": {
    "follow_up_agent_id": "b2e9c7a4-5d31-4f68-9c02-7a1b3e5d9f84",
    "follow_up_prompt": "Read the finished call. If the customer asked to be called back, greet them by name and confirm the slot they asked for.",
    "follow_up_extra_logic": {
      "skip_if_ordered": true
    }
  },
  "multilingual": {
    "enabled": true,
    "active_language": "hi",
    "languages": {
      "hi": {
        "agent_name": "Priya"
      },
      "en": {
        "agent_name": "Priya"
      }
    }
  },
  "calling_guard": {
    "enabled": true,
    "timezone": "Asia/Kolkata",
    "start": "09:00",
    "end": "21:00",
    "days": [
      0,
      1,
      2,
      3,
      4
    ],
    "spread": "window"
  },
  "approved_by": {
    "member_id": "member-live-9a8b7c6d",
    "role": "admin",
    "at": "2026-10-06T10:52:10.411Z",
    "request_id": "apr_3f9c1d2e4b5a6978c0d1",
    "number": 12
  },
  "approved_at": "2026-10-06T10:52:10.411Z",
  "dropped_languages": [
    "ta"
  ]
}