Build an Agent That Calls REST APIs
An AI agent becomes far more useful when it can reach external services. LM-Kit.NET's built-in HttpTool gives an agent the ability to call any REST API: fetch weather data, check order status, query internal dashboards, or post to webhooks. Combined with the JsonTool for parsing responses, you can build agents that integrate with the real world without writing custom tool code.
TL;DR
Minimal code to get an agent calling REST APIs:
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LM model = LM.LoadFromModelID("qwen3:8b");
var agent = Agent.CreateBuilder(model)
.WithPersona("API Assistant")
.WithInstruction("You can make HTTP requests to external APIs using the http tool. " +
"Parse JSON responses with the json tool.")
.WithPlanning(PlanningStrategy.ReAct)
.WithTools(tools =>
{
tools.Register(BuiltInTools.Http);
tools.Register(BuiltInTools.Json);
})
.WithMaxIterations(10)
.Build();
var result = await agent.RunAsync("What is the current weather in Paris? Use https://wttr.in/Paris?format=j1");
Console.WriteLine(result.Content);
The rest of this guide walks through each piece in detail, including security hardening and error handling.
Why This Matters
Two enterprise problems that agent-driven REST API access solves:
- Natural language interfaces to existing services. Your organization already has REST APIs for inventory, CRM, support tickets, and monitoring. Instead of building a dedicated UI for every new query, an agent can interpret a plain-English request, call the right endpoint, parse the response, and return a human-readable answer.
- Multi-step API orchestration. Some tasks require calling one API, extracting a value from the response, and feeding it into a second API. Agents with HTTP and JSON tools handle this automatically through their reasoning loop, eliminating the need for manual scripting.
Prerequisites
| Requirement | Minimum |
|---|---|
| .NET SDK | 8.0+ |
| VRAM | 6+ GB (for an 8B model with tool-calling support) |
| Disk | ~5 GB free for model download |
You need a model that supports tool calling. Recommended: qwen3:8b.
Step 1: Create the Project
dotnet new console -n RestApiAgent
cd RestApiAgent
dotnet add package LM-Kit.NET
Step 2: Configure the HTTP Tool
The HttpTool lets an agent make HTTP requests. By default, it enforces a 30-second timeout, a 100K character response limit, and blocks requests to localhost.
Default instance (no configuration needed):
using LMKit.Agents.Tools.BuiltIn;
// Default HttpTool: 30s timeout, 100K response limit, blocks localhost
var httpTool = BuiltInTools.Http;
Custom configuration with HttpToolOptions:
using LMKit.Agents.Tools.BuiltIn;
using LMKit.Agents.Tools.BuiltIn.Net;
var httpTool = BuiltInTools.CreateHttp(new HttpToolOptions
{
Timeout = TimeSpan.FromSeconds(15),
MaxResponseSize = 50000,
DefaultUserAgent = "MyApp-Agent/1.0",
AllowedHosts = new HashSet<string> { "api.weather.gov", "api.example.com" },
BlockedHosts = new HashSet<string> { "localhost", "127.0.0.1", "0.0.0.0" }
});
| Option | Default | Description |
|---|---|---|
Timeout |
30 seconds | Maximum time for a single HTTP request |
MaxResponseSize |
100000 chars | Truncates responses that exceed this limit. Set to 0 for unlimited |
DefaultUserAgent |
"LMKit-Agent/1.0" |
User-Agent header sent with every request |
AllowedSchemes |
http, https | Protocols the tool is permitted to use |
AllowedHosts |
null (allow all) | When set, only these hosts can be reached |
BlockedHosts |
localhost, 127.0.0.1, 0.0.0.0 | Hosts the tool will refuse to contact |
Step 3: Build the Agent
Combine the HTTP and JSON tools with a well-crafted instruction prompt. The instruction tells the agent how and when to use each tool:
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.Agents.Tools.BuiltIn.Net;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
// ──────────────────────────────────────
// 1. Load model
// ──────────────────────────────────────
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine($"\n Tool calling supported: {model.HasToolCalls}\n");
// ──────────────────────────────────────
// 2. Build the agent with HTTP + JSON tools
// ──────────────────────────────────────
var agent = Agent.CreateBuilder(model)
.WithPersona("REST API Assistant")
.WithInstruction(
"You help users interact with external REST APIs. " +
"Use the http tool to make GET, POST, PUT, PATCH, or DELETE requests. " +
"Use the json tool to parse, query, and format JSON responses. " +
"Always check the HTTP status code before interpreting the response body. " +
"If a request fails, explain the error clearly.")
.WithPlanning(PlanningStrategy.ReAct)
.WithTools(tools =>
{
tools.Register(BuiltInTools.Http);
tools.Register(BuiltInTools.Json);
tools.Register(BuiltInTools.DateTime);
})
.WithMaxIterations(10)
.Build();
Console.WriteLine("Agent ready.\n");
The agent now has three tools:
| Tool | Purpose |
|---|---|
http |
Make HTTP requests (GET, POST, PUT, PATCH, DELETE, HEAD) |
json |
Parse, query, format, validate, and inspect JSON data |
datetime |
Get the current date and time for time-sensitive API calls |
Step 4: Run the Agent
Send a prompt that requires calling an external API:
// ──────────────────────────────────────
// 3. Run the agent
// ──────────────────────────────────────
var result = await agent.RunAsync(
"What is the current weather in Paris? Use the API at https://wttr.in/Paris?format=j1");
if (result.IsSuccess)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Assistant: {result.Content}");
Console.ResetColor();
// Show tool usage
if (result.ToolCalls.Count > 0)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($" Tools used: {string.Join(", ", result.ToolCalls.Select(tc => tc.ToolName))}");
Console.WriteLine($" Duration: {result.Duration.TotalSeconds:F1}s, Inferences: {result.InferenceCount}");
Console.ResetColor();
}
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Error: {result.Error?.Message}");
Console.ResetColor();
}
The agent will:
- Recognize it needs to call an HTTP endpoint.
- Use the
httptool to send a GET request tohttps://wttr.in/Paris?format=j1. - Use the
jsontool to extract temperature, humidity, and conditions from the JSON response. - Return a human-readable summary.
Step 5: Restrict API Access
In production, you should lock down which hosts the agent can reach. The AllowedHosts option acts as a whitelist, and BlockedHosts acts as a blacklist.
Whitelist approach (recommended for production):
using LMKit.Agents.Tools.BuiltIn;
using LMKit.Agents.Tools.BuiltIn.Net;
// Only allow requests to your approved API endpoints
var restrictedHttp = BuiltInTools.CreateHttp(new HttpToolOptions
{
AllowedHosts = new HashSet<string>
{
"api.weather.gov",
"api.internal.yourcompany.com",
"orders.yourcompany.com"
}
});
var agent = Agent.CreateBuilder(model)
.WithPersona("Secure API Assistant")
.WithInstruction(
"You can only access approved company APIs. " +
"Available endpoints: api.weather.gov, api.internal.yourcompany.com, orders.yourcompany.com.")
.WithPlanning(PlanningStrategy.ReAct)
.WithTools(tools =>
{
tools.Register(restrictedHttp);
tools.Register(BuiltInTools.Json);
})
.WithMaxIterations(10)
.Build();
Blacklist approach (block sensitive hosts):
var safeHttp = BuiltInTools.CreateHttp(new HttpToolOptions
{
BlockedHosts = new HashSet<string>
{
"localhost", "127.0.0.1", "0.0.0.0",
"169.254.169.254", // AWS metadata endpoint
"metadata.google.internal" // GCP metadata endpoint
}
});
Step 6: Handle Errors and Timeouts
Check Agent Results
Always check result.IsSuccess before using the response:
var result = await agent.RunAsync("Fetch the latest exchange rates from https://api.exchangerate.host/latest");
if (result.IsSuccess)
{
Console.WriteLine(result.Content);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Agent failed: {result.Error?.Message}");
Console.ResetColor();
}
Configure Timeouts
Set a shorter timeout for APIs that should respond quickly:
var fastHttp = BuiltInTools.CreateHttp(new HttpToolOptions
{
Timeout = TimeSpan.FromSeconds(10)
});
Use CancellationToken
Pass a cancellation token to abort long-running agent executions:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
try
{
var result = await agent.RunAsync("Query all active orders from the internal API", cts.Token);
if (result.IsSuccess)
Console.WriteLine(result.Content);
}
catch (OperationCanceledException)
{
Console.WriteLine("Request timed out after 60 seconds.");
}
Complete Example
Full Program.cs that builds an interactive REST API agent:
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.Agents.Tools.BuiltIn.Net;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
// ──────────────────────────────────────
// 1. Load model
// ──────────────────────────────────────
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine($"\n Tool calling supported: {model.HasToolCalls}\n");
// ──────────────────────────────────────
// 2. Configure the HTTP tool
// ──────────────────────────────────────
var httpTool = BuiltInTools.CreateHttp(new HttpToolOptions
{
Timeout = TimeSpan.FromSeconds(20),
MaxResponseSize = 50000,
BlockedHosts = new HashSet<string>
{
"localhost", "127.0.0.1", "0.0.0.0",
"169.254.169.254"
}
});
// ──────────────────────────────────────
// 3. Build the agent
// ──────────────────────────────────────
var agent = Agent.CreateBuilder(model)
.WithPersona("REST API Assistant")
.WithInstruction(
"You help users interact with external REST APIs. " +
"Use the http tool to make GET, POST, PUT, PATCH, or DELETE requests to any URL the user provides. " +
"Use the json tool to parse and query JSON responses. " +
"Always check the HTTP status code before interpreting the body. " +
"If a request fails, explain the error and suggest a fix.")
.WithPlanning(PlanningStrategy.ReAct)
.WithTools(tools =>
{
tools.Register(httpTool);
tools.Register(BuiltInTools.Json);
tools.Register(BuiltInTools.DateTime);
})
.WithMaxIterations(10)
.Build();
// ──────────────────────────────────────
// 4. Interactive loop
// ──────────────────────────────────────
Console.WriteLine("REST API Agent ready. Type a request (or 'quit' to exit):\n");
Console.WriteLine("Example prompts:");
Console.WriteLine(" - What is the weather in Tokyo? Use https://wttr.in/Tokyo?format=j1");
Console.WriteLine(" - GET https://jsonplaceholder.typicode.com/posts/1 and summarize it");
Console.WriteLine(" - POST to https://httpbin.org/post with body {\"message\": \"hello\"}\n");
while (true)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("You: ");
Console.ResetColor();
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("quit", StringComparison.OrdinalIgnoreCase))
break;
var result = await agent.RunAsync(input);
if (result.IsSuccess)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\nAssistant: {result.Content}");
Console.ResetColor();
if (result.ToolCalls.Count > 0)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($" Tools used: {string.Join(", ", result.ToolCalls.Select(tc => tc.ToolName))}");
Console.WriteLine($" Duration: {result.Duration.TotalSeconds:F1}s, Inferences: {result.InferenceCount}");
Console.ResetColor();
}
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"\nError: {result.Error?.Message}");
Console.ResetColor();
}
Console.WriteLine();
}
Common Patterns
| Pattern | Code |
|---|---|
| GET a JSON API | "Fetch data from https://api.example.com/users/1" |
| POST with a JSON body | "POST to https://httpbin.org/post with body {\"name\": \"Alice\", \"role\": \"admin\"}" |
| Custom headers for authentication | "GET https://api.example.com/orders with header Authorization: Bearer abc123" |
| Extract a nested JSON field | Agent calls http to fetch, then json with query data.items[0].name |
| Check API health | "Send a HEAD request to https://api.example.com/health and report the status code" |
The agent interprets these natural language instructions, selects the right HTTP method, constructs the request, and uses the JSON tool to extract specific fields from the response.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Agent never calls the http tool | Model does not support tool calling | Check model.HasToolCalls. Use qwen3:8b or larger |
| Request blocked by AllowedHosts | Target host not in the whitelist | Add the host to AllowedHosts in HttpToolOptions |
| Response truncated | Response exceeds MaxResponseSize |
Increase MaxResponseSize or set to 0 for unlimited |
| Timeout on slow APIs | Default 30s timeout too short | Set a longer Timeout in HttpToolOptions |
| Agent loops without progress | Instruction prompt too vague | Be explicit about which APIs and endpoints the agent should use |
| JSON parsing fails | API returned non-JSON content (HTML, XML) | Tell the agent to check contentType before parsing |
| Agent calls localhost | Default BlockedHosts not set | Use BlockedHosts to block localhost, 127.0.0.1, and 0.0.0.0 |
Next Steps
- Create an AI Agent with Tools: learn the full Agent + Tools architecture with custom ITool implementations.
- Build a Resilient Production Agent: add retry logic, timeouts, and circuit breakers for production deployments.
- Connect to MCP Servers from Your Application: integrate with external tool servers using the Model Context Protocol.
- Intercept and Control Tool Invocations: log, validate, or block tool calls before they execute.