Your agent builds great output. Structured reports, data visualizations, interactive tools, dynamically generated landing pages. And then, what? You log it to a file? Render it locally? There's a missing step between "agent produces HTML" and "user sees a real page." Pubby is that step.
One API call. HTML in, live URL out. Under 3 seconds.
Every agent that produces HTML output faces the same dead end: there's nowhere built for the output to go. Options people try and why they fall short:
Data URIWorks in a browser, breaks in chat interfaces, unusable as a shareable link.
Self-hosted serverRequires infra you have to maintain and scale; overkill for output that might only be viewed once.
Vercel/Netlify CLINeeds a Git repo, project config, and a build step that assumes your HTML is part of a larger project.
GitHub GistRaw HTML, not rendered; anyone clicking the link sees source code.
Email attachmentRenders inconsistently, can't be linked, can't be updated.
Pubby's publish endpoint accepts HTML (or a full file bundle) and returns a live URL. That's the entire API surface for the publish operation.
Request
POST https://api.pubby.au/publish
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"html": "<html><body><h1>Hello from my agent</h1></body></html>",
"slug": "agent-output-001"
}Response
{
"url": "https://pubby.au/agent-output-001",
"slug": "agent-output-001",
"published_at": "2026-02-22T12:00:00Z"
}The URL is live immediately. Not "eventually consistent." Immediately.
curl -X POST https://api.pubby.au/publish \
-H "Authorization: Bearer $PUBBY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<html><body><h1>Report</h1><p>Content here</p></body></html>",
"slug": "my-report"
}'Here's a complete workflow pattern used by teams running HTML-generating agents:
User request
↓
LLM (Claude/GPT/Gemini)
- Receives prompt + data
- Generates structured HTML output
↓
Post-processing (optional)
- Inject custom CSS
- Add analytics snippet
- Sanitize user-supplied content
↓
Pubby API: POST /publish
↓
Live URL returned
↓
Return URL to user / store in database / send in emailThe whole cycle, from user request to live URL, takes 5-10 seconds depending on LLM latency. Pubby's contribution to that is under 3 seconds.
Need to publish 100 pages at once? Pubby handles it. The API supports parallel requests up to your plan's rate limit.
import asyncio
import httpx
import os
async def publish_batch(pages: list[dict]) -> list[str]:
"""
pages: list of {"html": "...", "slug": "..."}
Returns: list of live URLs
"""
async with httpx.AsyncClient() as client:
tasks = [
client.post(
"https://api.pubby.au/publish",
headers={"Authorization": f"Bearer {os.environ['PUBBY_API_KEY']}"},
json=page,
)
for page in pages
]
responses = await asyncio.gather(*tasks)
return [r.json()["url"] for r in responses]
# Generate 100 product pages
product_pages = [
{
"html": generate_product_page(product),
"slug": f"product-{product['id']}",
}
for product in products
]
urls = asyncio.run(publish_batch(product_pages))
print(f"Published {len(urls)} pages")| Plan | Publishes / hour | Concurrent requests | Max file size |
|---|---|---|---|
| Free | 60 | 5 | 10MB bundle |
| Pro ($12/mo) | 600 | 20 | 50MB bundle |
| Teams ($25/mo) | 1,500 | 50 | 100MB bundle |
| Business ($45/mo) | 3,000+ | 100+ | 200MB bundle |
Rate limits apply to publish operations only. Published pages have no traffic limits. Use the rate limit headers in API responses to implement backoff:
def publish_with_backoff(html: str, slug: str, max_retries: int = 3) -> str:
for attempt in range(max_retries):
response = requests.post(
"https://api.pubby.au/publish",
headers={"Authorization": f"Bearer {os.environ['PUBBY_API_KEY']}"},
json={"html": html, "slug": slug},
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()["url"]
raise Exception("Max retries exceeded")Beyond publishing, the API lets you update and delete pages:
# Update an existing page (republish to same slug)
requests.post(
"https://api.pubby.au/publish",
headers={"Authorization": f"Bearer {os.environ['PUBBY_API_KEY']}"},
json={"html": new_html, "slug": "existing-slug"}, # overwrites
)
# Delete a page
requests.delete(
"https://api.pubby.au/pages/existing-slug",
headers={"Authorization": f"Bearer {os.environ['PUBBY_API_KEY']}"},
)
# List published pages
pages = requests.get(
"https://api.pubby.au/pages",
headers={"Authorization": f"Bearer {os.environ['PUBBY_API_KEY']}"},
).json()S3 and similar services can host static files. But you'd need to:
That's 2-4 hours of setup and ongoing infrastructure management. Pubby does all of it in the 3 seconds before it hands you back a URL.
Rotate keys anytime from the dashboard. Each key can be given a label so you know which application is using it.