Cookbook

Crypto data for a Discord bot

A slash command that answers from a live feed. The data half is four lines; the rest is Discord's embed format, which is the part people actually get stuck on.

~20 min Python, discord.pyNo API key, no signup
/items/news/trending//items/news/
01

The data call

No key, no auth header, no signup — which is what makes this viable for a community bot that might be installed in a hundred servers before anyone thinks about quotas.

curl "https://api.alphaday.com/items/news/trending/?limit=3"
02

The slash command

Sentiment maps cleanly onto Discord's embed colour, which is the whole reason this reads better than a link dump: the channel sees the mood before it reads the headline.

import discord, aiohttp from discord import app_commands API = "https://api.alphaday.com/items/news/" COLOURS = {2: 0x2ecc71, 1: 0x87d37c, 0: 0x95a5a6, -1: 0xe6a23c, -2: 0xe74c3c} client = discord.Client(intents=discord.Intents.default()) tree = app_commands.CommandTree(client) @tree.command(name="crypto", description="Latest news for a project") async def crypto(interaction: discord.Interaction, project: str = ""): params = {"limit": 3, **({"tags": project} if project else {})} async with aiohttp.ClientSession() as s: async with s.get(API, params=params) as r: items = (await r.json())["results"] if not items: # A tag with no keywords matches nothing. Say so, rather than # rendering an empty embed that looks like the bot is broken. await interaction.response.send_message( f"No tagged coverage for `{project}`.", ephemeral=True) return embeds = [ discord.Embed( title=i["title"][:256], url=i["url"], colour=COLOURS.get(i.get("sentiment"), 0x95a5a6), ).set_footer(text=f"{i['source']['name']} · sentiment {i['sentiment_score']}") for i in items ] await interaction.response.send_message(embeds=embeds)

What it prints

Produced by running the code above against the live API, not written by hand.

$ curl -s "https://api.alphaday.com/items/news/?tags=ethereum&limit=3" sentiment=-2 score=-0.74 Ethereum and Base abandon joint account abstraction standard sentiment= 1 score= 0.46 BitMine Buys $68M in Ethereum, Nears 6 Million ETH sentiment= 1 score= 0.10 Bitcoin options lead $16.6B Q3 crypto expiry -> renders as three embeds: one red, two green

The live response behind the command, 15 Sep 2026. sentiment is the integer that picks the colour; sentiment_score is the decimal shown in the footer. Note the third row: a Bitcoin options headline came back under tags=ethereum, because one article legitimately carries several project tags.

Variations

The same shape, pointed somewhere else. Every figure below came back from the live API.

Trending instead of latest

/items/news/trending/ ranks by engagement across the corpus rather than by recency. Better for a /whatsup command that should return something interesting rather than something new.

API = "https://api.alphaday.com/items/news/trending/"

A governance channel

Point the same embed builder at /items/dao/ and colour by time remaining instead of sentiment. Proposals carry ends_at, which is the field that makes urgency renderable.

async with s.get("https://api.alphaday.com/items/dao/",
params={"sources": "arbitrum_dao", "limit": 5}) as r:

Autocomplete the project argument

The tag taxonomy is queryable, so the project argument can autocomplete against real slugs rather than letting users guess — which is what produces the empty result the code above has to handle.

await s.get("https://api.alphaday.com/tags/", params={"search": current})

Before you build on this

Some tags return zero for a project that clearly has coverage

tags=lido returns 0 governance items. A tag only matches content through its keywords, and a tag filed with none never matches anything. Check a tag returns results before building a scheduled job on it.

Sentiment is per article, not per project

Each news item carries sentiment (-2 to +2) and sentiment_score. There is no project-level mood field — averaging is your call, and a mean over 50 articles can read positive in a week whose single biggest story scored -2.