01What a data source is
A data source is a web address you save once. When a viewer runs a command that quotes it, the bot calls that address, takes one value out of the answer, and says it in chat. Weather, a game stat, the queue length on your own server: if it answers over https and returns JSON or plain text, a command can read it.
PrincessBarney !weather
ItsBagelBot It is 21°C in Montreal right now.
Behind that line is an ordinary custom command. Its response, as typed in the editor:
It is {urlfetch:weather}°C in Montreal right now.
The token names the source, not the value. Which value the bot pulls out is saved on the
source itself, as a path: an address inside the answer. The response is
the building, current is the floor, temperature_2m is the
door. Write it out with dots and you get current.temperature_2m. Section
three does that clicking for you, and the analogy can go home now.
02Add one from the command editor
Everything happens inside Commands, in the editor that docks beside your command list.
- Put the cursor where the value goes
Open a command, click into Response, and leave the cursor at the spot the number belongs in the sentence.
- Open the palette
Under the response, beside the
{user}and{args}pills, sits a chip labelled Data source. - Start a new one
+ New data source opens the modal titled Add a data source: "Point it at any web API, fetch one real response, then click the value you want to show in chat."
- Name it and paste the address
Display name is for you. It auto-slugs into Data source name, which is the word that goes inside the token: "Lower-case letters, digits and underscores. Used inside {urlfetch:name}." Then Web address, https, up to 512 characters.
- Fetch one real response
Fetch a sample calls your API for real, about once every 10 seconds. If it answers in a shape the modal cannot read, take the link or paste a response instead and paste one in by hand.
- Click the value, then add the source
The answer becomes a tree under "Click the value you want to show in chat." Click
temperature_2mand the modal reads Showing current.temperature_2m. Add source saves it, and the palette row inserts{urlfetch:weather}at your cursor.
Add a data source
Point it at any web API, fetch one real response, then click the value you want to show in chat.
Lower-case letters, digits and underscores. Used inside {urlfetch:name}.
Click the value you want to show in chat.
- 01 Display name is the one you read in lists. It auto-slugs into the data source name below.
- 02 Data source name is the word inside the token: lower-case letters, digits and underscores, up to 32 characters.
- 03 Web address: https, absolute, up to 512 characters. The bot sends it exactly as typed, every time.
- 04 Fetch a sample makes a real request against your API. Roughly one test every 10 seconds.
- 05 Clicking a value saves its path on the source. "Use the whole response instead" switches to plain text.
Once a source exists, the same chip lists it. Every saved source shows its path so you can tell two weather feeds apart at a glance, and clicking a row drops the token where your cursor was.
Insert variable
- 01 The Data source chip sits with the token pills under Response, next to Counter.
- 02 Each row shows the path saved on the source, or "Plain text" when it prints the whole answer.
- 03 + New data source opens the modal without losing the command you are writing.
03Picking the value
This is the same tree the dashboard shows, with the same rules. Only values are
clickable; a branch like current holds other things, so it cannot be the
end of a path. Edit the response on the left and the tree follows, which is the fastest
way to rehearse your own API before you save anything.
Saved on the source
Token you type
Path spelled in the token
Chat would showcut here, 100 bytes
Plain text mode skips the path entirely. The bot trims the answer and shows its first 100 bytes.
| Rule | What it means |
|---|---|
| Dots between steps | current.temperature_2m opens current, then takes temperature_2m out of it. |
| Lists use bare digits | items.0.name is the first entry. Square brackets are not part of the grammar. |
| Depth | Up to 8 steps. Past that the tree refuses the value: "Deeper than 8 levels. Pick something closer to the top." |
| Each step | Letters, digits, underscore and hyphen, up to 64 characters each. |
| The end of the path | Lands on a value: text, a number, true or false. Stopping on an object, a list or an empty field counts as a broken data source, and chat shows your |default, or the raw token if there is none. |
| A path inside the token | Wins over the one saved on the source. {urlfetch:weather.current.wind_speed_10m} reads a different value from the same address. |
| The name | Case-insensitive. {URLFETCH:Weather} and {urlfetch:weather} are the same source. |
| The value | Cleaned, trimmed, then cut at 100 bytes before it reaches chat. |
{urlfetch:weather} on current.temperature_2m for
!weather, then write
{urlfetch:weather.current.wind_speed_10m} in !wind.
Same source, same 20 data source budget, two different answers.04APIs that need a key
Some APIs want a key before they answer. Add it once in Settings, on the API keys panel: a label up to 32 characters so you recognise it later, and the secret itself, up to 512 characters, then Add key. The panel's own hint says it best: "API keys your data sources send with each request. People with shared access can use them but never see them."
The secret is sealed before it is written down and it is never shown back. You get the
label and the last four characters, which is enough to tell two keys apart when you
rotate one. When a data source is attached to a key, the bot sends it as an
Authorization: Bearer header on every fetch, and the address itself stays
clean.
In the "Add a data source" modal, pick the key in the API key field. "No key needed" is the right answer for most public APIs. If you have no key on file yet, the field says "No API keys yet. Add one in Settings, then pick it here." and an Open API keys link takes you to Settings.
05Limits and timing
The numbers below are the live ones. You will meet the cache first: an answer the bot liked is reused for 30 seconds, so a command that runs 40 times in a minute still calls your API twice.
| Limit | The number |
|---|---|
| Data sources per channel | 20. |
| Data sources in one response | 3. The editor refuses to save a fourth. |
| Web address | https only, absolute, up to 512 characters. |
| Addresses refused | IP literals, localhost, and anything ending in .local or .internal. Checked when you save and again on every fetch. |
| Response size | The bot reads up to 1 MiB of an answer, measured after decompression. |
| Sample size | The editor shows a tree only for a sample of readable text up to 128 KB. A larger or unreadable answer gives "The API answered, but its response could not be read automatically. Paste one below instead." A pasted sample over 128 KB gives "That sample is over 128 KB. Paste a shorter one." |
| Content type | application/json or text/*. |
| Timeouts | The API gets 2.5 seconds, the fetch service 3 seconds, and the bot stops waiting at 3.5 seconds. |
| Cache | A good answer is held 30 seconds. A refusal, like a 404 or a path that found nothing, is held 15 seconds. An outage is not cached. |
| Requests per channel | 6 a minute, counted across every data source you own. |
| Requests per data source | 30 a minute. |
| Requests per API host | 120 a minute, counted across every channel pointed at that host. |
| Redirects | Up to 3 hops, each one staying on https. |
| A host that keeps failing | Five transport failures in a row and that host is rested for 60 seconds. |
| "Fetch a sample" | About one test every 10 seconds: "Too many test runs. Each one calls the real API. Wait about 10 seconds and try again." |
- Requests your API actually receives
- 2 per minute
- Answered from the 30-second cache
- 10 per minute
- Refused by the 6 per minute channel cap
- 0 per minute
Those runs show nothing where the value goes, or your fallback.
The cache exists for your API quota. Without it, one raid could spend a month of calls in an evening, and every other channel pointed at the same host would feel it too.
06What chat shows when it fails
A command never goes silent because of a data source. The rest of the sentence is sent
and the value is left blank, unless you wrote a fallback after a pipe:
{urlfetch:weather|unavailable} says "unavailable" when the fetch
fails. A source that is paused, missing or unable to produce a value works the same way: chat and
timers show the token's |default. Without a |default, chat
shows the raw token exactly as typed, and that is why a raw
{urlfetch:weather} in chat means "check this data source".
| What happened | What chat shows |
|---|---|
| The API refused, it rate limited you, it returned an error, it took longer than the bot waits, the path found nothing, or the message is a replay of an older one | Nothing where the value goes, or your |fallback text. |
| The source is paused, missing, never saved, or could not produce a value | Your |fallback text. With no |fallback, the token printed exactly as typed: {urlfetch:weather}. |
PrincessBarney!weather
ItsBagelBotIt is °C in Montreal right now.
Your key was rejected, or the endpoint turned the request away. Test it in the dashboard and you get the same verdict: "The API refused the request. Check the URL, and pick the right key under API key if this API needs one."
A cap was reached: 6 fetches a minute for the channel, 30 for this data source, or 120 a minute for that API across every channel using it. Nothing is broken and the next minute works again.
The bot waits 3.5 seconds and then speaks without the value. A slow API under load is the usual reason.
The path was fine but the answer had nothing at the end of it, usually because the API renamed a field. Open the source, fetch a sample, and click the value again.
A paused or missing source has nothing to expand. A token with a |default shows that default. A token without one, like this one, is printed as typed. The dashboard says it when a source cannot produce a value: "This data source could not produce a value. Until it does, chat shows the token's |default value if it has one, otherwise {urlfetch:name} as typed. Check the web address and the API key, then click Fetch a sample again."
Three data sources in one response is the editor limit and it refuses to save a fourth. The bot keeps its own ceiling at eight and prints every token past it exactly as typed, like this one.
{urlfetch:weather|unavailable}. Then a slow API reads as
"unavailable" instead of a gap in the sentence.If something goes wrong
| What you see | What it means | What to do |
|---|---|---|
| The API refused the request. Check the URL, and pick the right key under API key if this API needs one. | The API turned the test away: a wrong or missing key, or a wrong address. | Check the web address. If the API needs a key, add it in Settings under API keys, pick it in the API key field, and test again. |
| Rate limited. Wait a moment and test again. | A request cap was reached. | Wait a minute and test again. |
| The API returned an error. Chat sees nothing, unless the token has a |default: {urlfetch:name|default}. | The API answered with an error. | Check the API status, or add a fallback after a pipe. |
| The API timed out. Chat sees nothing, unless the token has a |default: {urlfetch:name|default}. | The API took longer than the bot waits. | Try again, and add a fallback after a pipe. |
| This data source could not produce a value. Until it does, chat shows the token's |default value if it has one, otherwise {urlfetch:name} as typed. Check the web address and the API key, then click Fetch a sample again. | The source is paused, or its path or address cannot give a value. | Resume the source if it is paused. Otherwise fix the address or key, fetch a sample, and click the value again. |
| The fetch service did not answer. Try again in a moment. | The service that calls your API did not reply in time. | Wait a moment and test again. |
| The API answered, but its response could not be read automatically. Paste one below instead. | The answer is not readable text, or it is over 128 KB. | Click "or paste a response instead" and paste a real response by hand. |
| That is not valid JSON. Fix it and the tree appears here. | The pasted sample is not JSON. | Paste the response again, from the first brace to the last. |
| That sample is over 128 KB. Paste a shorter one. | The pasted sample is too large for the tree. | Paste a smaller slice of the response. |
| Too many test runs. Each one calls the real API. Wait about 10 seconds and try again. | Fetch a sample allows about one test every 10 seconds. | Wait 10 seconds. |
| You've reached the 20 data source limit for your channel. Delete one to make room. | A channel holds 20 data sources. | Delete a source you no longer use. |
| A data source named "name" already exists. | Another source already has that name. | Pick a different name, or edit the existing source. |
| A timer message can reference at most 3 different fetched values ({urlfetch:…}). | Timer messages are capped at 3 data sources. | Quote fewer sources in the timer message. |
| A raw {urlfetch:name} in chat | The source is paused, missing, or cannot produce a value, and the token has no |default. Tokens past the per-message cap also stay raw. | Open the command, check the name matches a saved source, and resume it if paused. Add a |default so chat shows a fallback instead. |
07What it will not do
-
The address is fixed when you save it.
{args}and{user}are left alone inside a web address, so a viewer cannot steer the request at your API. -
Requests are
GET, and headers are not yours to set. The one the bot adds isAuthorization: Bearer, and only when the source carries a key. - Tokens expand in custom command responses, after the permission, live and cooldown checks have passed, and in timer messages, each time the timer fires. A timer message can quote up to 3 different data sources. Counters leave the token alone.
- A replayed chat message never fetches twice. If the bot re-reads an older event, chat gets a blank (or your fallback) rather than a second call to your API.
- Private and local addresses are turned down at both ends: when you save, and again at fetch time.
{urlfetch:weather} in the sentence and
{urlfetch:weather.current.wind_speed_10m} right after it: two
values, one request, one line of your quota.