Skip to main content

Wallu - Developer API

Wallubot provides an easy-to-use API for developers to integrate Wallu into other platforms and projects.

Creating an Addon

  1. Go to the addons page and create a new API key
  2. Use the API key in your addon to send messages to Wallu
    • The API is designed to be simple and easy to use
    • Easy to add Wallu to any other platform and will work just like the Discord bot

Restricting what knowledge a public API key can reach

A public key is meant to be embedded in a frontend, so anyone who visits your site can copy it. The channel.id and addon.name in each /on-message request are just claims from whoever holds the key, and Wallu uses both to decide which knowledge to answer from - they select the channel's settings (custom instructions, ticketing) and unlock FAQs and documents you restricted to a specific channel or addon.

So by default, someone with your public key can send a different channel.id or addon.name and get answers from knowledge you meant for another channel - for example a private Discord channel's FAQs.

If you restrict FAQs/documents per channel, pin each public key to what it actually sends, on the addons page under Restrict knowledge:

  • Allowed channels - the channel.id values the key may send, one per line
  • Allowed addons - the addon.name values the key may send, one per line (use the plain name, exactly as you send it)

* works as a wildcard for dynamic values, e.g. /docs/*. An empty field allows anything for it, so set both to fully restrict a key - restricting only channels still lets the key claim any addon name, and vice versa. Requests outside the allowed values are rejected with 403.

Existing keys are unrestricted until you set this. The panel only shows the setting for public keys (private keys live server-side, so restricting them is rarely needed), but any key that has restrictions set is enforced the same way.

Documentation

Example: Use Wallu's AI to answer a question anywhere

Node.js

const axios = require('axios')
// or "import axios from 'axios'"

const API_URL = 'https://api.wallubot.com/v1'

async function walluExample() {

const response = await axios.post(`${API_URL}/on-message`, {
addon: {
name: 'my-addon',
version: '0.1.0',
},
channel: {
// You can generate one, for example, using the current group chat name/ID or generate a random one. The bot may use this to keep track of previous messages.
id: '123456789',
// For example, in the Wallu Telegram addon this is the group chat name
name: 'Addon Channel #1'
},
user: {
// Some kind of identifier for this user. The bot may use this to keep track of previous messages.
id: '123456789',
// The username of the user (the bot may use this to personalize the response)
username: 'John Doe',
// Whether the user is a staff member. You can also just leave this false. (This mainly just affects some settings like the "avoid answering staff members unless mentioned")
is_staff_member: false,
},
message: {
// ID of the message, or just generate a random ID for this.
id: '123456789',
// If true, the bot is forced to answer, otherwise it may not answer.
is_bot_mentioned: true,
// The message content
content: 'how do I reset my password??'
},
configuration: {
// The emoji type to use. If set to 'none', the bot will not use any emojis.
emoji_type: 'unicode',
// Whether to include the sources links in the response.
include_sources: false,
}
}, {
headers: {
// Find your API key here: https://panel.wallubot.com/addons
// It's OK to use **public** API key on your website
// (public API key does not let users change bot behaviour, like upload documents)
// If you restrict FAQs/documents per channel, also restrict the key to the channel.id
// and addon.name it sends - see "Restricting what knowledge a public API key can reach" above.
// You should never share **private** API keys.
"X-API-Key": process.env.WALLU_API_KEY,
}
})
console.log(response.data)
// {
// response: {
// message: 'Click "forgot my password" on the login page. For more information, you can visit the following link: <https://example.com>.'
// }
// }
}

walluExample().then(() => console.log("DONE!"))

Example: Sync GitHub files as a document

This example shows how you could, for example, sync all *.md and *.txt files from your GitHub repository as one Wallu document and keep it up to date. This can be more reliable than importing a website and updates immediately.

On first run, it will create a new document (with your-external-id ID - CHANGE IT) and later it will update the same document.

Steps

  1. You probably already have a github repository with some markdown files.
  2. Go to the Wallu's addons page and create a new private API key (uploading documents needs full access)
  3. Go to your GitHub repository settings and add a new secret with the key WALLUBOT_API_KEY and the value of the API key you created in step 2.
  4. Create a new GitHub Actions workflow file in your repository (e.g., .github/workflows/sync-wallu-document.yml) with the following content.
  5. Customize the FILE_PATTERNS and EXTERNAL_DOCUMENT_ID variables in the workflow file.
  6. Commit and push the changes to your repository.

Help with GitHub actions: GitHub Actions Documentation

Note: Wallu has a limit for the document size. Note 2: Currently, if you remove (or move) a file in the repository, the removal won't be reflected in Wallu. You may need to manually clean up deleted files in the Wallu panel if needed. Or delete all + sync all again.

.github/workflows/sync-wallu-document.yml
name: Update Wallu's Documentation
on:
push:
branches:
- main # Change this to your main branch
jobs:
upsert-wallu-documents:
runs-on: ubuntu-latest
env:
# Customize FILE_PATTERNS by listing the glob patterns of files you wish to include
# Examples:
# '**/**.md' -> Include all Markdown files ("**" = including subdirectories)
# 'config/*.{yaml,yml}' -> Include YAML (.yaml and .yml) files from "config" directory ("*" = not in subdirectories)
FILE_PATTERNS: |
**/*.md
config/*.{yaml,yml}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Find and upload files individually
env:
# Add WALLUBOT_API_KEY to your repository secrets
# You can create it here: https://panel.wallubot.com/addons?createApiKey=private
# DO NOT ADD IT HERE DIRECTLY (OR PUBLISH IT ANYWHERE)
API_KEY: ${{ secrets.WALLUBOT_API_KEY }}
# No need to change the API_ENDPOINT
API_ENDPOINT: "https://api.wallubot.com/v1/document/upsert"
run: |
# NOTE: This workflow uploads each file individually using the file path + name as the external key.
# It will NOT remove files from Wallu that have been deleted from the repository.
# You may need to manually clean up deleted files in the Wallu panel if needed.

mkdir -p wallu_output
temp_file_list=$(mktemp)

# Enable globstar for ** patterns
shopt -s globstar nullglob

# Process each pattern from FILE_PATTERNS
echo "$FILE_PATTERNS" | sed '/^[[:space:]]*$/d' | while IFS= read -r pattern; do
# Remove leading/trailing whitespace
pattern=$(echo "$pattern" | xargs)
if [[ -n "$pattern" && "$pattern" != \#* ]]; then
echo "Processing pattern: '$pattern'"
if [[ "$pattern" == !* ]]; then
# Handle exclusion patterns - skip for now in this simple approach
echo "Exclusion pattern detected: $pattern"
else
# Use shell globbing for file matching
found_files=false
eval "for file in $pattern; do
if [[ -f \"\$file\" ]]; then
echo \"Found file: \$file\"
echo \"\$file\" >> \"$temp_file_list\"
found_files=true
fi
done"
if [[ "$found_files" == "false" ]]; then
echo "No files found for pattern: $pattern"
fi
fi
fi
done

# Check if any files were found
if [[ ! -s "$temp_file_list" ]]; then
echo "No files found matching the patterns"
exit 1
fi

# Deduplicate and sort the file list
sort -u "$temp_file_list" > wallu_output/sorted_unique_files.txt

# Upload each file individually
upload_success=true
while IFS= read -r filename; do
echo "Processing file: $filename"

# Use the full file path as the external document ID
# Replace forward slashes with underscores to create a valid ID
external_id=$(echo "$filename" | sed 's/\//_/g')

# Create a header for the file content
file_header="# File: $filename"$'\n\n'
file_content=$(cat "$filename")
full_content="${file_header}${file_content}"

# Create JSON payload
jq -n --arg content "$full_content" '{content: $content}' > wallu_output/payload.json

# Make the API call for this individual file
echo "Uploading to external ID: $external_id"
response=$(curl -w "%{http_code}" -s -X PUT "$API_ENDPOINT/$external_id" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
--data-binary @wallu_output/payload.json)

http_code="${response: -3}"
if [[ "$http_code" -ge 200 && "$http_code" -lt 300 ]]; then
echo "Successfully uploaded: $filename"
else
echo "Failed to upload: $filename (HTTP code: $http_code)"
echo "Response: ${response%???}"
upload_success=false
fi

done < wallu_output/sorted_unique_files.txt

# Cleanup
rm -f "$temp_file_list" wallu_output/sorted_unique_files.txt wallu_output/payload.json

if [[ "$upload_success" != "true" ]]; then
echo "One or more uploads failed"
exit 1
fi

echo "All files uploaded successfully"

List and download documents

The counterpart to /document/upsert: list your documents and download any document's raw text as a file. Handy for exporting everything, editing in bulk, or a two-way "sync this folder" flow - pull a document to a file, edit it locally, then push it back with upsert. These endpoints need a private key (sk_...), like upsert.

API_URL="https://api.wallubot.com/v1"

# List all your documents (metadata only, no content)
curl -s "$API_URL/documents" -H "X-API-Key: $WALLU_API_KEY"
# -> { "documents": [ { "id": "...", "external_id": "docs/setup.md", "name": "...", "read_only": false, "active": true, "content_length": 2451 }, ... ] }

# Download one document's raw text to a file (by external_id or internal id)
curl -s "$API_URL/document/docs/setup.md" -H "X-API-Key: $WALLU_API_KEY" -o setup.md

# Edit setup.md however you like, then push it back:
jq -Rs '{content: .}' setup.md \
| curl -s -X PUT "$API_URL/document/upsert/docs/setup.md" \
-H "X-API-Key: $WALLU_API_KEY" -H "Content-Type: application/json" --data-binary @-

Notes:

  • Every request needs your API key in the X-API-Key header (private sk_... or an mcp_... key) - there is no unauthenticated document access. If you're driving this from an AI/agent, it must have the raw key value to put in the header.
  • Look-up is by external_id first (the id you upserted with), falling back to the document's internal id, so both work.
  • Imported documents (Discord/website/Git) can be downloaded but not edited via upsert - their content comes from the source and would be overwritten on the next sync.

Example Projects

See Example Projects for open-source integrations (Telegram, Minecraft, website widget) you can use or fork.