Add custom functionality to your Browserable tasks
curl -X POST https://api.browserable.ai/api/v1/task/create \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"task": "Log into my account",
"agent": "BROWSER_AGENT",
"tools": [
{
"type": "function",
"function": {
"name": "form_input",
"description": "Get user input for login credentials",
"parameters": {
"type": "object",
"properties": {
"heading": {
"type": "string",
"description": "Form heading"
},
"description": {
"type": "string",
"description": "Form description"
},
"fields": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": { "type": "string" },
"label": { "type": "string" },
"placeholder": { "type": "string" }
}
}
}
}
}
}
}
]
}'
curl -X GET https://api.browserable.ai/api/v1/task/TASK_ID/run/status \
-H "x-api-key: YOUR_API_KEY"
{
"success": true,
"data": {
"status": "running",
"detailedStatus": "tool_call",
"toolCall": {
"toolCallId": "unique string to identify this call",
"tool": {
"name": "form_input", // this will be the tool function name you have provided
"parameters": {
// Tool call parameters in line with your definition
}
}
}
}
}
curl -X POST https://api.browserable.ai/api/v1/task/TASK_ID/run/tool-input \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "[{\"label\":\"Username\",\"value\":\"user@example.com\"},{\"label\":\"Password\",\"value\":\"password123\"}]",
"toolCallId": "TOOL_CALL_ID"
}'
const createTaskWithForm = async () => {
const response = await fetch(
"https://api.browserable.ai/api/v1/task/create",
{
method: "POST",
headers: {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
task: "Login to website",
agent: "BROWSER_AGENT",
tools: [
{
type: "function",
function: {
name: "form_input",
description: `Use this tool to ask information from the user. This is the only way to get information from the user. And assume that user might not have any other immediate context. So any relevant/ related information user needs can be provided to the user using this.
When to use this:
- If you are stuck and you need help from user
- Captchas that you are not able to solve yourself
- Logins, Signups etc.`,
parameters: {
type: "object",
properties: {
heading: {
type: "string",
description: "The heading of the form input",
},
description: {
type: "string",
description: "The description of the form input",
},
fields: {
type: "array",
items: {
type: "object",
properties: {
type: {
type: "string",
description:
"What type of input to show to the user",
enum: [
"text",
"number",
"email",
"tel",
"url",
"password",
"date",
"time",
"datetime-local",
"month",
"week",
"textarea",
"select",
"checkbox",
"radio",
"switch",
"range",
],
},
label: {
type: "string",
description: "Label text for the input field",
},
placeholder: {
type: "string",
description:
"Placeholder text shown when field is empty",
},
defaultValue: {
type: ["string", "number", "boolean"],
description: "Default value for the field",
},
readonly: {
type: "boolean",
description: "Whether the field is read-only",
default: false,
},
required: {
type: "boolean",
description: "Whether the field is required",
default: false,
},
disabled: {
type: "boolean",
description: "Whether the field is disabled",
default: false,
},
min: {
type: "number",
description:
"Minimum value for number/range/date inputs",
},
max: {
type: "number",
description:
"Maximum value for number/range/date inputs",
},
step: {
type: "number",
description: "Step value for number/range inputs",
},
minLength: {
type: "number",
description: "Minimum length for text inputs",
},
maxLength: {
type: "number",
description: "Maximum length for text inputs",
},
pattern: {
type: "string",
description:
"Regular expression pattern for validation",
},
options: {
type: "array",
description:
"Options for select/radio/checkbox inputs",
items: {
type: "object",
properties: {
label: {
type: "string",
description: "Display label for the option",
},
value: {
type: ["string", "number"],
description: "Value for the option",
},
},
},
},
multiple: {
type: "boolean",
description:
"Allow multiple selections for select input",
default: false,
},
rows: {
type: "number",
description: "Number of rows for textarea",
},
cols: {
type: "number",
description: "Number of columns for textarea",
},
autocomplete: {
type: "string",
description: "Autocomplete attribute value",
},
className: {
type: "string",
description:
"CSS class names to apply to the input",
},
},
required: ["type"],
},
},
submitButton: {
type: "object",
properties: {
label: {
type: "string",
description: "Label text for the submit button",
},
},
},
},
},
},
},
],
}),
}
);
return await response.json();
};
{
"heading": "Login Credentials",
"description": "Please enter your login credentials",
"fields": [
{
"type": "text",
"label": "Username",
"placeholder": "Enter your username",
"required": true
},
{
"type": "password",
"label": "Password",
"placeholder": "Enter your password",
"required": true
},
{
"type": "checkbox",
"label": "Remember me",
"placeholder": "Keep me logged in",
"required": false
}
],
"submitButton": {
"label": "Login"
}
}
import React, { useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
const FormInput = ({ formData, onSubmit }) => {
const [formState, setFormState] = useState(
formData.fields.reduce((acc, field) => {
acc[field.label] = field.defaultValue || '';
return acc;
}, {})
);
const handleInputChange = (label, value) => {
setFormState(prev => ({
...prev,
[label]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
const formattedData = formData.fields.map(field => ({
...field,
value: formState[field.label]
}));
onSubmit(formattedData);
};
const renderField = (field) => {
const commonClasses = "w-full px-3 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent";
switch (field.type) {
case 'textarea':
return (
<TextareaAutosize
minRows={field.rows || 3}
maxRows={field.maxRows || 10}
className={commonClasses}
placeholder={field.placeholder}
value={formState[field.label] || ''}
onChange={(e) => handleInputChange(field.label, e.target.value)}
disabled={field.disabled}
readOnly={field.readonly}
required={field.required}
/>
);
case 'select':
return (
<select
className={commonClasses}
value={formState[field.label] || ''}
onChange={(e) => handleInputChange(field.label, e.target.value)}
disabled={field.disabled}
required={field.required}
multiple={field.multiple}
>
<option value="">Select an option</option>
{field.options?.map((option, idx) => (
<option key={idx} value={option.value}>
{option.label}
</option>
))}
</select>
);
case 'checkbox':
return (
<div className="flex items-center">
<input
type="checkbox"
className="h-4 w-4 text-sm text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
checked={formState[field.label] || false}
onChange={(e) => handleInputChange(field.label, e.target.checked)}
disabled={field.disabled}
required={field.required}
/>
<span className="ml-2 text-sm text-gray-600">{field.placeholder}</span>
</div>
);
case 'radio':
return (
<div className="space-y-2">
{field.options?.map((option, idx) => (
<div key={idx} className="flex items-center">
<input
type="radio"
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
name={field.label}
value={option.value}
checked={formState[field.label] === option.value}
onChange={(e) => handleInputChange(field.label, e.target.value)}
disabled={field.disabled}
required={field.required}
/>
<span className="ml-2 text-sm text-gray-600">{option.label}</span>
</div>
))}
</div>
);
case 'switch':
return (
<label className="inline-flex items-center cursor-pointer">
<input
type="checkbox"
className="sr-only peer"
checked={formState[field.label] || false}
onChange={(e) => handleInputChange(field.label, e.target.checked)}
disabled={field.disabled}
/>
<div className="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-700">{field.placeholder}</span>
</label>
);
case 'range':
return (
<div className="flex flex-col">
<input
type="range"
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
min={field.min}
max={field.max}
step={field.step}
value={formState[field.label] || field.min || 0}
onChange={(e) => handleInputChange(field.label, e.target.value)}
disabled={field.disabled}
/>
<span className="text-sm text-gray-500 mt-1">
Value: {formState[field.label] || field.min || 0}
</span>
</div>
);
default:
return (
<input
type={field.type}
className={commonClasses}
placeholder={field.placeholder}
value={formState[field.label] || ''}
onChange={(e) => handleInputChange(field.label, e.target.value)}
min={field.min}
max={field.max}
step={field.step}
minLength={field.minLength}
maxLength={field.maxLength}
pattern={field.pattern}
autoComplete={field.autocomplete}
disabled={field.disabled}
readOnly={field.readonly}
required={field.required}
/>
);
}
};
const labelClasses = "block text-sm font-medium text-gray-700 mb-1";
const errorClasses = "text-red-500 text-xs mt-1";
return (
<form onSubmit={handleSubmit} className="space-y-6 w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow">
<div className="space-y-2">
<h2 className="text-xl font-bold text-gray-900">{formData.heading}</h2>
<p className="text-sm text-gray-500">{formData.description}</p>
</div>
<div className="space-y-4">
{formData.fields.map((field, index) => (
<div key={index} className="space-y-1">
<label className={labelClasses}>{field.label}</label>
{renderField(field)}
{field.description && (
<p className="text-sm text-gray-500 mt-1">{field.description}</p>
)}
</div>
))}
</div>
<div className="flex justify-end">
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
{formData.submitButton?.label || 'Submit'}
</button>
</div>
</form>
);
};
export default FormInput;
const submitFormResponse = async (taskId, formData) => {
const response = await fetch(
`https://api.browserable.ai/api/v1/task/${taskId}/run/tool-input`,
{
method: "POST",
headers: {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
input: JSON.stringify(formData),
toolCallId: "TOOL_CALL_ID", // Get this from the task status
}),
}
);
return await response.json();
};
text
: Standard text inputpassword
: Masked password inputemail
: Email input with validationnumber
: Numeric inputcheckbox
: Single checkboxradio
: Radio button groupselect
: Dropdown selectiontextarea
: Multi-line text inputswitch
: Toggle switchrange
: Range sliderplaceholder
, required
, min
, max
, etc.