Kalle Browser via the API
Kalle Browser is Kalle working in a real browser: it reads pages, clicks, types and fills in forms. Through the API you can hand it a task from your own code and decide which steps need a person's approval.
How it works
Tasks run in the Kalle Browser extension, which works in Chrome, Edge, Brave, Arc, Firefox and Safari. Install it and pair it with your workspace from Kalle Browser in the Appmake console. Tasks you create with the API then run in that paired browser.
Create a task
POST/v1/browser/tasks
Describe the goal in plain words, the way you would brief a colleague. Add start_url if you know where Kalle should begin.
curl https://api.kalle.build/v1/browser/tasks \
-H "Authorization: Bearer $KALLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kalle-turbo-max",
"instructions": "Find three venues in Copenhagen for 40 people on 12 March and add them to the shared sheet.",
"start_url": "https://example.com/venues",
"approval": "before_submit"
}'const response = await fetch("https://api.kalle.build/v1/browser/tasks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KALLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "kalle-turbo-max",
instructions: "Find three venues in Copenhagen for 40 people on 12 March and add them to the shared sheet.",
start_url: "https://example.com/venues",
approval: "before_submit"
}),
});
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.kalle.build/v1/browser/tasks",
headers={"Authorization": f"Bearer {os.environ['KALLE_API_KEY']}"},
json={
"model": "kalle-turbo-max",
"instructions": "Find three venues in Copenhagen for 40 people on 12 March and add them to the shared sheet.",
"start_url": "https://example.com/venues",
"approval": "before_submit"
},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{
"model": "kalle-turbo-max",
"instructions": "Find three venues in Copenhagen for 40 people on 12 March and add them to the shared sheet.",
"start_url": "https://example.com/venues",
"approval": "before_submit"
}`)
req, _ := http.NewRequest("POST", "https://api.kalle.build/v1/browser/tasks", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KALLE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}require "json"
require "net/http"
uri = URI("https://api.kalle.build/v1/browser/tasks")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("KALLE_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = <<~JSON
{
"model": "kalle-turbo-max",
"instructions": "Find three venues in Copenhagen for 40 people on 12 March and add them to the shared sheet.",
"start_url": "https://example.com/venues",
"approval": "before_submit"
}
JSON
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))<?php
$body = <<<'JSON'
{
"model": "kalle-turbo-max",
"instructions": "Find three venues in Copenhagen for 40 people on 12 March and add them to the shared sheet.",
"start_url": "https://example.com/venues",
"approval": "before_submit"
}
JSON;
$ch = curl_init("https://api.kalle.build/v1/browser/tasks");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("KALLE_API_KEY"),
"Content-Type: application/json",
],
]);
echo curl_exec($ch);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class KalleExample {
public static void main(String[] args) throws Exception {
String body = """
{
"model": "kalle-turbo-max",
"instructions": "Find three venues in Copenhagen for 40 people on 12 March and add them to the shared sheet.",
"start_url": "https://example.com/venues",
"approval": "before_submit"
}
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.kalle.build/v1/browser/tasks"))
.header("Authorization", "Bearer " + System.getenv("KALLE_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}| Field | Type | Required | Description |
|---|---|---|---|
instructions | string | Yes | What Kalle should get done. |
model | string | No | The model that drives the browser. Defaults to auto. |
start_url | string | No | The page Kalle opens first. |
approval | string | No | When Kalle stops and asks: before_submit or every_step. Defaults to before_submit. |
The response is the task, with an id and a status:
{
"id": "task_8Hq2",
"type": "browser_task",
"status": "queued",
"model": "kalle-turbo-max",
"created_at": "2026-11-07T09:12:44Z"
}Follow the task
GET/v1/browser/tasks/{id}
Check the task until it is finished. Each step Kalle has taken is listed in steps, with what it saw and what it did.
| Status | Meaning |
|---|---|
queued | Waiting for the paired browser. |
running | Kalle is working. |
waiting_for_approval | Kalle has stopped before a step and needs a decision. |
done | Finished. The result is in result. |
failed | Kalle could not finish. The reason is in error. |
cancelled | Someone stopped the task. |
Approve a step
With before_submit, Kalle always asks before it pays for anything or sends a form. With every_step, it asks before every action. The step that waits is in pending_step, with a description you can show to a person.
POST/v1/browser/tasks/{id}/approve
curl https://api.kalle.build/v1/browser/tasks/task_8Hq2/approve \
-H "Authorization: Bearer $KALLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "step_id": "step_5", "decision": "approve" }'const response = await fetch("https://api.kalle.build/v1/browser/tasks/task_8Hq2/approve", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KALLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ step_id: "step_5", decision: "approve" }),
});
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.kalle.build/v1/browser/tasks/task_8Hq2/approve",
headers={"Authorization": f"Bearer {os.environ['KALLE_API_KEY']}"},
json={"step_id": "step_5", "decision": "approve"},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{ "step_id": "step_5", "decision": "approve" }`)
req, _ := http.NewRequest("POST", "https://api.kalle.build/v1/browser/tasks/task_8Hq2/approve", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KALLE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}require "json"
require "net/http"
uri = URI("https://api.kalle.build/v1/browser/tasks/task_8Hq2/approve")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("KALLE_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = <<~JSON
{ "step_id": "step_5", "decision": "approve" }
JSON
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))<?php
$body = <<<'JSON'
{ "step_id": "step_5", "decision": "approve" }
JSON;
$ch = curl_init("https://api.kalle.build/v1/browser/tasks/task_8Hq2/approve");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("KALLE_API_KEY"),
"Content-Type: application/json",
],
]);
echo curl_exec($ch);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class KalleExample {
public static void main(String[] args) throws Exception {
String body = """
{ "step_id": "step_5", "decision": "approve" }
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.kalle.build/v1/browser/tasks/task_8Hq2/approve"))
.header("Authorization", "Bearer " + System.getenv("KALLE_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}Send approve to let Kalle carry on, or reject to stop that step. After a rejection Kalle looks for another way or ends the task and explains why.
Good tasks
- Say what done looks like: "add three venues to the sheet" is better than "look for venues".
- Give limits up front, such as a budget, a date or the sites Kalle may use.
- Keep a person in the loop for anything with money or customers involved.