#How to call external REST APIs (Sanity CMS) from an agent — best approach?
83 messages · Page 1 of 1 (latest)
Short version: for arbitrary REST APIs, yes, the usual way is a small script or wrapper that OpenClaw runs via exec.
Built-in options:
web_fetchfor simple public HTTP GET page fetching, not full REST clients, and it does not do JS. Docs: https://docs.openclaw.ai/tools/web-fetchbrowserfor JS-heavy sites, but not ideal as a generic API client
So for Sanity:
- if you just need a simple public read,
web_fetchmight be enough - if you need auth headers, POST/PUT/PATCH, custom JSON handling, pagination, etc., use one of these:
- a small script +
exec - a custom skill/plugin
- an MCP/server wrapper if you want a cleaner long-term integration
- a small script +
exec docs: https://docs.openclaw.ai/tools/exec
If you want, I can sketch the smallest good pattern for Sanity, either as a quick script or as a reusable skill.
i want to make post call so webfetch wont work right ? and how to create a plugin like in plugin i can call the api or what i cant see docs saying it
Yep, correct, web_fetch is GET-only, so it is not the right tool for a POST call. Docs: https://docs.openclaw.ai/tools/web-fetch
For what you want, there are really 2 good paths:
-
Small script +
exec
Fastest and simplest for one API integration. -
Plugin that registers a real tool
Better if you want something reusable likesanity_post(...).
My honest recommendation: start with a script first. A plugin is nicer long-term, but heavier.
Important distinction
- Skill = instructions for the agent
- Plugin = actual code that adds a new tool/capability
So if you need to actually call a REST API, a plugin can do that, yes.
Minimal plugin shape
Docs: https://docs.openclaw.ai/plugins/building-plugins
Plugin install/config docs: https://docs.openclaw.ai/tools/plugin
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { Type } from "@sinclair/typebox";
export default definePluginEntry({
id: "sanity-api",
name: "Sanity API",
description: "Adds a tool for Sanity POST requests",
register(api) {
api.registerTool({
name: "sanity_post",
description: "Send a POST request to Sanity",
parameters: Type.Object({
url: Type.String(),
body: Type.Any(),
}),
async execute(_id, params) {
const res = await fetch(params.url, {
method: "POST",
headers: {
"content-type": "application/json",
// auth header here
},
body: JSON.stringify(params.body),
});
const text = await res.text();
return {
content: [{ type: "text", text }],
};
},
});
},
});
Then install it
openclaw plugins install ./my-plugin
openclaw gateway restart
Practical advice
- For auth tokens, do not pass them in chat
- Put secrets in plugin config or env vars
- If this is just one Sanity workflow, script +
execis way less painful
If you want, I can give you either:
- a tiny script-based Sanity POST setup, or
- a full minimal OpenClaw plugin folder you can copy-paste.
i want plugin thing so i cna reuse it explain step by step approach on how to do that
Yep, if you want the reusable version, make a tool plugin.
Here’s the clean step-by-step path I’d use.
1. Create a plugin folder
mkdir -p ~/my-sanity-plugin
cd ~/my-sanity-plugin
You’ll make these files:
my-sanity-plugin/
├── package.json
├── openclaw.plugin.json
└── index.ts
Docs:
- Plugin quickstart: https://docs.openclaw.ai/plugins/building-plugins
- Plugin install/config: https://docs.openclaw.ai/tools/plugin
- SDK entrypoints: https://docs.openclaw.ai/plugins/sdk-entrypoints
2. Add package.json
{
"name": "@you/openclaw-sanity-plugin",
"version": "0.0.1",
"type": "module",
"openclaw": {
"extensions": ["./index.ts"],
"compat": {
"pluginApi": ">=2026.3.24-beta.2",
"minGatewayVersion": "2026.3.24-beta.2"
}
}
}
3. Add openclaw.plugin.json
This is the manifest. OpenClaw requires it.
{
"id": "sanity-api",
"name": "Sanity API",
"description": "Custom Sanity REST tool",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"baseUrl": { "type": "string" },
"token": { "type": "string" }
},
"required": ["baseUrl", "token"]
},
"uiHints": {
"token": {
"label": "Sanity token",
"sensitive": true
}
}
}
Docs: https://docs.openclaw.ai/plugins/manifest
4. Add index.ts
This registers a real tool the agent can call.
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { Type } from "@sinclair/typebox";
export default definePluginEntry({
id: "sanity-api",
name: "Sanity API",
description: "Custom Sanity REST tool",
register(api) {
api.registerTool(
{
name: "sanity_post",
description: "Send a POST request to the configured Sanity API",
parameters: Type.Object({
path: Type.String({ description: "Path appended to baseUrl" }),
body: Type.Any({ description: "JSON body to send" })
}),
async execute(_id, params) {
const cfg = api.pluginConfig as {
baseUrl?: string;
token?: string;
};
if (!cfg.baseUrl || !cfg.token) {
throw new Error(
"Missing plugin config. Set plugins.entries.sanity-api.config.baseUrl and token"
);
}
const res = await fetch(`${cfg.baseUrl}${params.path}`, {
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${cfg.token}`
},
body: JSON.stringify(params.body)
});
const text = await res.text();
return {
content: [
{
type: "text",
text: JSON.stringify(
{
ok: res.ok,
status: res.status,
body: text
},
null,
2
)
}
]
};
}
},
{ optional: true }
);
}
});
Why optional: true? Because this tool does writes, and the docs recommend optional tools for side-effecting stuff.
5. Install the plugin locally for development
Use the local link mode while iterating:
openclaw plugins install -l ~/my-sanity-plugin
Then restart:
openclaw gateway restart
6. Enable it in config
Add this in your openclaw.json:
{
plugins: {
entries: {
"sanity-api": {
enabled: true,
config: {
baseUrl: "https://YOUR_PROJECT.api.sanity.io/v2025-02-19",
token: "YOUR_SANITY_TOKEN"
}
}
}
},
tools: {
allow: ["sanity_post"]
}
}
Because the tool is optional, you must allow it.
7. Restart the gateway again
openclaw gateway restart
If the current chat session does not see the tool immediately, start a new session/thread.
8. Test it
Now ask the agent something like:
Use
sanity_postto call/data/mutate/productionwith this JSON body: …
That should make the agent call your plugin tool instead of trying web_fetch.
9. Improve it once the first version works
After the basic version works, add:
sanity_get- better error handling
- typed params instead of
Type.Any() - separate tools for mutate/query/assets
- env/secret handling instead of plain token in config
10. Publish later if you want reuse across machines
When it works locally, you can publish it to npm or ClawHub so openclaw plugins install ... works anywhere.
If you want, I can give you a Sanity-specific version next, for example:
sanity_querysanity_mutatesanity_create_document
That would be a nicer plugin than one generic sanity_post tool.
then in skill do i need to emntion call this tool or something like tha
Yep, if you add a skill, the skill should mention when to use the tool.
But important bit:
- Plugin = creates the tool
- Skill = teaches the agent when/how to use it
So:
- if your plugin registers
sanity_post, the agent can already use it - a skill is optional, but it usually makes usage much more reliable
Minimal example skill
---
name: sanity_helper
description: Use the Sanity tool for content mutations and API requests.
---
When the user asks to create, update, publish, or mutate content in Sanity, use the `sanity_post` tool.
Guidelines:
- Use `/data/mutate/production` for mutations unless the user specifies another dataset/path.
- Build a valid JSON body before calling the tool.
- If required info is missing, ask for it before calling the tool.
- After the tool returns, summarize the result clearly.
So do you need a skill?
- No, not strictly
- Yes, if you want the agent to use your tool consistently and correctly
My recommendation
- make the plugin first
- then add a small skill that says when to call
sanity_post
Docs:
- Skills: https://docs.openclaw.ai/tools/creating-skills
- Skills reference: https://docs.openclaw.ai/tools/skills
If you want, I can give you the exact SKILL.md + plugin combo for a Sanity setup.
yes
Nice. Here’s the smallest useful plugin + skill combo I’d use for Sanity.
1. Plugin, adds a real tool
Create a folder like ~/my-sanity-plugin.
package.json
{
"name": "@you/openclaw-sanity-plugin",
"version": "0.0.1",
"type": "module",
"openclaw": {
"extensions": ["./index.ts"],
"compat": {
"pluginApi": ">=2026.3.24-beta.2",
"minGatewayVersion": "2026.3.24-beta.2"
}
}
}
openclaw.plugin.json
{
"id": "sanity-api",
"name": "Sanity API",
"description": "Sanity mutation tool",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"baseUrl": { "type": "string" },
"token": { "type": "string" },
"dataset": { "type": "string" }
},
"required": ["baseUrl", "token"]
},
"uiHints": {
"token": {
"label": "Sanity token",
"sensitive": true
}
}
}
index.ts
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { Type } from "@sinclair/typebox";
function tryParse(text: string) {
try {
return JSON.parse(text);
} catch {
return text;
}
}
export default definePluginEntry({
id: "sanity-api",
name: "Sanity API",
description: "Sanity mutation tool",
register(api) {
api.registerTool(
{
name: "sanity_mutate",
description: "Send mutations to Sanity CMS",
parameters: Type.Object({
dataset: Type.Optional(Type.String()),
mutations: Type.Array(Type.Any(), { minItems: 1 })
}),
async execute(_id, params) {
const cfg = api.pluginConfig as {
baseUrl?: string;
token?: string;
dataset?: string;
};
if (!cfg.baseUrl || !cfg.token) {
throw new Error(
"Missing plugin config. Set plugins.entries.sanity-api.config.baseUrl and token"
);
}
const dataset = params.dataset ?? cfg.dataset ?? "production";
const res = await fetch(`${cfg.baseUrl}/data/mutate/${dataset}`, {
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${cfg.token}`
},
body: JSON.stringify({
mutations: params.mutations
})
});
const text = await res.text();
return {
content: [
{
type: "text",
text: JSON.stringify(
{
ok: res.ok,
status: res.status,
dataset,
body: tryParse(text)
},
null,
2
)
}
]
};
}
},
{ optional: true }
);
}
});
2. Install the plugin locally
openclaw plugins install -l ~/my-sanity-plugin
openclaw gateway restart
3. Enable it in openclaw.json
{
plugins: {
entries: {
"sanity-api": {
enabled: true,
config: {
baseUrl: "https://YOUR_PROJECT.api.sanity.io/v2025-02-19",
token: "YOUR_SANITY_TOKEN",
dataset: "production"
}
}
}
},
tools: {
allow: ["sanity_mutate"]
}
}
Because the tool is optional, you need tools.allow.
4. Skill, teaches the agent when to use it
In the workspace for that agent, create:
skills/sanity-helper/SKILL.md
---
name: sanity_helper
description: Use the sanity_mutate tool for Sanity CMS write operations.
---
When the user asks to create, update, patch, or delete content in Sanity CMS, use the `sanity_mutate` tool.
Rules:
- Prefer the configured dataset unless the user explicitly asks for another one.
- Build valid Sanity mutation objects before calling the tool.
- If required info is missing, ask for it first.
- After the tool returns, summarize whether it succeeded and include important ids or errors.
Common mutation shapes:
- Create:
{ "create": { "_type": "post", "title": "Hello" } }
- Create if not exists:
{ "createIfNotExists": { "_id": "post-123", "_type": "post", "title": "Hello" } }
- Patch:
{ "patch": { "id": "post-123", "set": { "title": "Updated title" } } }
- Delete:
{ "delete": { "id": "post-123" } }
Then restart or start a new session so the skill is picked up.
5. How it works together
- plugin creates
sanity_mutate - skill tells the model when to call
sanity_mutate
So yes, the skill should explicitly mention the tool name.
6. Example prompt to test
Try asking the agent:
Create a Sanity post titled "Hello World" with
_idpost-1
The skill should push it toward calling sanity_mutate.
If you want, next I can give you a better v2 with:
sanity_querysanity_mutate- typed params instead of
Type.Any() - safer auth via env/secret refs
wha tis the other option we need to run the script and ask openclaw to run those script or what
Yep, there are 2 different approaches:
Option 1, script + exec
You write a script, for example sanity.js or sanity.py, and then the agent runs it with the exec tool.
Flow:
- user asks for a Sanity action
- skill says “use the script”
- OpenClaw runs the script
- script calls the API
So in this option, yes, OpenClaw is basically running your script.
Option 2, plugin tool
You do not run a separate script.
Instead:
- you build a plugin
- plugin registers a tool like
sanity_mutate - agent calls that tool directly
- the tool code does the API request inside OpenClaw
So here the flow is:
- user asks for a Sanity action
- skill says “use
sanity_mutate” - OpenClaw calls the plugin tool directly
No external script needed.
Short version
- Script option = “make a script and let OpenClaw run it”
- Plugin option = “make a tool and let OpenClaw call it directly”
Since you said you want something reusable, plugin is the cleaner option.
If you want, I can draw the exact flow for both in one tiny example so it clicks immediately.
ok ok tell me what all i nee to do with this plugin
Yep, if you go the plugin route, this is the full checklist:
What you need to do
-
Create a plugin folder
package.jsonopenclaw.plugin.jsonindex.ts
-
In
index.ts, register a tool- example:
sanity_mutate - that tool does the
fetch(..., { method: "POST" })
- example:
-
Install the plugin into OpenClaw
openclaw plugins install -l /path/to/your/plugin
-
Enable the plugin in
openclaw.json- add
plugins.entries.sanity-api.enabled: true - add your config like
baseUrl,token,dataset
- add
-
Allow the tool
- if you made it optional, add:
tools: { allow: ["sanity_mutate"] } -
Add a skill
- create
skills/sanity-helper/SKILL.md - tell the agent:
- when to use
sanity_mutate - what kind of requests it should build
- when to use
- create
-
Restart the gateway
openclaw gateway restart -
Start a new session/thread
- so the agent picks up the plugin + skill cleanly
-
Test it
- ask something like:
- “Create a Sanity post with title X”
- “Update document Y in Sanity”
- ask something like:
-
Later, improve/publish it
- add more tools like
sanity_query - publish to npm/ClawHub if you want reuse across machines
So the minimum pieces are
- plugin code
- plugin config
- tool allowlist if optional
- skill
- restart
- test
And no, with this route you do not need a separate script.
If you want, I can make it even simpler and give you a super short 5-file starter template you can copy exactly.
i have amin skill can i say in that only to use this plugin why to create other skill
Yes, absolutely, you can put it in your existing admin skill.
You do not need a separate skill if:
- that skill is already relevant
- you’re okay keeping Sanity instructions inside it
So the setup can be:
- plugin registers
sanity_mutate - your existing admin skill says when to use
sanity_mutate
Why I mentioned a separate skill
Only for cleanliness:
- separate skill = easier to reuse
- separate skill = less clutter in your admin skill
- separate skill = easier to debug later
But functionally, your current skill is fine.
What to add in your admin skill
Something like:
When the user asks to create, update, patch, or delete content in Sanity CMS, use the `sanity_mutate` tool.
Prefer the configured dataset unless the user explicitly asks for another one.
Ask for missing ids/fields before calling the tool.
Summarize the result after the tool returns.
So yeah, short answer:
- plugin is still needed
- new skill is optional
- you can reuse your existing admin skill
After editing the skill, start a new session so OpenClaw reloads it.
When I run openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin I get this error:
Config validation failed: plugins.entries.sanity-api.config.baseUrl: invalid config: must have required property 'baseUrl'
I haven't added the plugin to openclaw.json yet — do I need to add the config there before running the install command? Or should the install work without config first?
Short answer: the install is failing because your plugin schema says baseUrl is required.
So right now OpenClaw is validating plugins.entries.sanity-api.config, sees no baseUrl, and stops.
What I’d do
Best fix
Make the plugin config optional in the manifest, and check it at runtime inside the tool instead.
So change openclaw.plugin.json from this:
"required": ["baseUrl", "token"]
to no required array at all, like this:
{
"id": "sanity-api",
"name": "Sanity API",
"description": "Sanity mutation tool",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"baseUrl": { "type": "string" },
"token": { "type": "string" },
"dataset": { "type": "string" }
}
}
}
Then keep your runtime check in index.ts:
if (!cfg.baseUrl || !cfg.token) {
throw new Error("Missing plugin config...");
}
That way:
- plugin installs cleanly
- config can be added after
- tool fails only when actually used without config
Then do this order
- loosen the manifest schema
- run:
openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin - add config to
openclaw.json - restart:
openclaw gateway restart
Alternative
If you want to keep baseUrl and token required in schema, then yes, you need the config present before the plugin can validate cleanly.
But honestly, for a local dev plugin, I’d make them optional in schema.
One more thing
You may already have a half-created plugins.entries.sanity-api entry now. If it keeps failing even after changing the manifest, check that section too.
If you want, paste:
openclaw --versionopenclaw config get plugins.entries.sanity-api
and I’ll tell you exactly what state it’s in.
"I'm getting Cannot find module 'openclaw/plugin-sdk/plugin-entry' — what is the correct import path for definePluginEntry and @sinclair/typebox on a Lightsail OpenClaw install?"
The correct import path is already:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { Type } from "@sinclair/typebox";
So the error is probably not the path, it is usually one of these:
Most likely fix
For a standalone plugin, put both in dependencies, not devDependencies:
{
"dependencies": {
"openclaw": "YOUR_OPENCLAW_VERSION",
"@sinclair/typebox": "^0.34.0"
}
}
Then install them in the plugin folder with:
cd ~/.openclaw/plugins/sanity-plugin
pnpm add openclaw @sinclair/typebox
Then reinstall/link the plugin again:
openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin
openclaw gateway restart
Why this happens
OpenClaw docs say plugin installs use:
npm install --omit=dev --ignore-scripts
So if openclaw or @sinclair/typebox are only in devDependencies, runtime cannot see them.
One more important detail
Set the openclaw package version to match your installed OpenClaw as closely as possible.
Please run:
openclaw --version
If you paste that, I can tell you what version to put in package.json.
Docs:
- SDK overview: https://docs.openclaw.ai/plugins/sdk-overview
- Building plugins: https://docs.openclaw.ai/plugins/building-plugins
OpenClaw 2026.3.23 (ccfeecb)
Ah, that helps, you’re on 2026.3.23, and the example I gave was from newer docs. So first thing, change your plugin package to match your installed OpenClaw version.
Use this in package.json
{
"name": "@you/openclaw-sanity-plugin",
"version": "0.0.1",
"type": "module",
"dependencies": {
"openclaw": "2026.3.23",
"@sinclair/typebox": "^0.34.0"
},
"openclaw": {
"extensions": ["./index.ts"],
"compat": {
"pluginApi": ">=2026.3.23",
"minGatewayVersion": "2026.3.23"
}
}
}
Import lines should stay
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { Type } from "@sinclair/typebox";
Then run
cd ~/.openclaw/plugins/sanity-plugin
pnpm install
openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin
openclaw gateway restart
Important
The earlier 2026.3.24-beta.2 compat block is wrong for your box, so remove that.
If it still says module not found
Then your installed 2026.3.23 build likely does not expose that SDK subpath cleanly, and the easiest fix is to update OpenClaw to a newer version that matches the docs examples.
If you want, paste your current package.json and I’ll sanity-check it before you retry.
do i need to run pnpm install here
Not strictly, no.
If you are using:
openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin
then OpenClaw should install the plugin’s runtime dependencies itself.
So the normal flow is just:
openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin
openclaw gateway restart
When pnpm install helps
Only as a local debugging step if you want to check the plugin folder itself.
So:
- normal usage: no, you usually do not need
pnpm install - debugging/dev: you can run it in the plugin folder
If you’re still getting Cannot find module, the more likely issue is:
- wrong
package.json - wrong OpenClaw version match
- missing dependency declaration in
dependencies
If you want, paste your current package.json and I’ll check it quickly.
GNU nano 7.2 /home/ubuntu/.openclaw/plugins/sanity-plugin/package.json
{
"name": "sanity-api",
"version": "0.0.1",
"type": "module",
"openclaw": {
"extensions": ["./index.ts"],
"compat": {
"pluginApi": ">=2026.3.24-beta.2",
"minGatewayVersion": "2026.3.24-beta.2"
}
}
}
check if this works
Not yet, I’d change it.
Problems in that file:
-
wrong compat version for your box
You’re on2026.3.23, not2026.3.24-beta.2 -
missing dependencies
You import:openclaw/plugin-sdk/plugin-entry@sinclair/typebox
so those need to be in
dependencies
Use this instead:
{
"name": "sanity-api",
"version": "0.0.1",
"type": "module",
"dependencies": {
"openclaw": "2026.3.23",
"@sinclair/typebox": "^0.34.0"
},
"openclaw": {
"extensions": ["./index.ts"],
"compat": {
"pluginApi": ">=2026.3.23",
"minGatewayVersion": "2026.3.23"
}
}
}
Then run:
openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin
openclaw gateway restart
So, short answer: the current package.json will probably not work.
If you want, paste index.ts next and I’ll check that too.
ubuntu@ip-172-26-13-239:~$ openclaw plugins install -l ~/.openclaw/plugins/sanity-plugin
13:14:11 [plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: sanity-api (/home/ubuntu/.openclaw/plugins/sanity-plugin/index.ts). Set plugins.allow to explicit trusted ids.
13:14:11 [plugins] sanity-api failed to load from /home/ubuntu/.openclaw/plugins/sanity-plugin/index.ts: Error: Cannot find module 'openclaw/plugin-sdk/plugin-entry'
Require stack:
- /home/ubuntu/.openclaw/plugins/sanity-plugin/index.ts
咽 OpenClaw 2026.3.23 (ccfeecb) — Shell yeah—I'm here to pinch the toil and leave you the glory.
13:14:12 [plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: sanity-api (/home/ubuntu/.openclaw/plugins/sanity-plugin/index.ts). Set plugins.allow to explicit trusted ids.
13:14:12 [plugins] sanity-api failed to load from /home/ubuntu/.openclaw/plugins/sanity-plugin/index.ts: Error: Cannot find module 'openclaw/plugin-sdk/plugin-entry'
Require stack: - /home/ubuntu/.openclaw/plugins/sanity-plugin/index.ts
Confi-> f21, backup=/home/ubuntu/.openclaw/openclaw.json.bak)
Linked plugin path: ~/.openclaw/plugins/sanity-plugin
Restart the gateway to load plugins. why am i seeing sha tag again and again
The ccfeecb bit is just the OpenClaw commit hash in the CLI banner, not the problem.
The real problem is this:
You put the plugin in an auto-discovery folder
You’re linking this path:
~/.openclaw/plugins/sanity-plugin
But ~/.openclaw/plugins/... is already a place OpenClaw scans for plugins.
So what’s happening is:
- OpenClaw sees
~/.openclaw/plugins/sanity-plugin/index.ts
- It tries to auto-load it directly
- It fails on
openclaw/plugin-sdk/plugin-entry - Then your
plugins install -l ...command continues and you see more logs
That’s why it looks repetitive.
What to do instead
Move the plugin source out of ~/.openclaw/plugins/.
For example:
mkdir -p ~/Projects
mv ~/.openclaw/plugins/sanity-plugin ~/Projects/sanity-plugin
Then link that:
openclaw plugins install -l ~/Projects/sanity-plugin
openclaw gateway restart
Also set an allowlist
That warning is separate, but worth fixing:
{
plugins: {
allow: ["sanity-api"]
}
}