Table of Contents

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:

  1. 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.
  2. 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:

  1. Recognize it needs to call an HTTP endpoint.
  2. Use the http tool to send a GET request to https://wttr.in/Paris?format=j1.
  3. Use the json tool to extract temperature, humidity, and conditions from the JSON response.
  4. 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