Build with Upivia

One HTTP endpoint to give your AI agent controlled access to email, LLMs, web search, voice, and code execution - with dollar budgets, policies, and a full audit trail.

5 minutes

Quickstart

  1. Sign up at /signup.
  2. Top up your wallet at /billing.
  3. Create an agent in /agents — copy the API key (shown once).
  4. Enable services in /permissions and set monthly budgets.
  5. POST to /v1/service-requests from your agent code.

Minimum working example:

bash
# Dispatch a request: send an email through the platform.
# Upivia handles policy + budget checks, debits your wallet,
# calls the underlying provider, and returns a structured result.
curl -X POST https://www.upivia.com/v1/service-requests \
  -H "Authorization: Bearer aw_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "email.send",
    "payload": {
      "to": "ada@example.com",
      "subject": "Hello from my agent",
      "body": "First message routed through Upivia."
    }
  }'

Response:

json
{
  "status": "executed",
  "request_id": "req_01HQ...",
  "cost_cents": 1,
  "balance_cents": 4999,
  "result": { "provider_message_id": "..." }
}
One endpoint, infinite agents

What you can build

Every app below is one agent + a few /v1/service-requestscalls. No keys to juggle, no per-provider billing, no abuse risk — every call is policy-checked, budgeted, and audit-logged.

Sales

Cold-outreach SDR

Research a lead, draft a personalized cold email, send it. Cap at $5/day so a runaway loop costs less than coffee.

web_search.querytext_generation.generateemail.send
Read tutorial →
Support

Triage + auto-reply bot

Classify inbound tickets, draft a reply, queue refunds over $50 for human approval via the built-in approvals queue.

text_generation.generateemail.send
Read tutorial →
Research

Deep-research assistant

Fan out 20 Tavily searches, summarize with GPT-4o, return a cited brief. Hard $2/run ceiling per user.

web_search.querytext_generation.generate
Read tutorial →
Ops

On-call voice escalator

When a Sentry alert fires, place a Twilio call to the on-call engineer and read the incident summary aloud.

voice_call.createtext_generation.generate
Read tutorial →
Content

Newsletter generator

Pull the week's top stories in a niche, write a 400-word digest, send to your list. One cron, one agent.

web_search.querytext_generation.generateemail.send
Read tutorial →
DevTools

Sandboxed code runner

Let users prompt "plot AAPL last 5 years". The agent writes Python, runs it in E2B, returns the chart. Per-user $0.50 cap.

code.executetext_generation.generate
Read tutorial →
Internal

Slack /ask bot

Wire one agent to your Slack slash-command. Search the web, summarize, post back. Pay once at the org level.

web_search.querytext_generation.generate
Read tutorial →
Growth

Lead-enrichment pipeline

For each new HubSpot contact: search LinkedIn, extract company + role, write back to CRM. Approval gate over 100 lookups/day.

web_search.querytext_generation.generate
Read tutorial →
Personal

Daily-briefing agent

Every morning at 7am: scan the news in your topics, summarize, email you. Costs about $0.03/day.

web_search.querytext_generation.generateemail.send
Read tutorial →
Marketing

MMS promo blast

Send an image + short copy via MMS to an opted-in list. Per-recipient idempotency keys keep retries safe.

mms.send
Read tutorial →
Auth

SMS 2FA fallback

When email 2FA bounces, fall through to SMS. Read inbound replies via sms.read for verification round-trips.

sms.sendsms.read
Read tutorial →
RAG

Managed knowledge base (RAG)

Upsert docs into a managed vector store, query with natural language, hand top chunks to GPT-4o. No Pinecone account needed.

knowledge.upsertknowledge.queryknowledge.list_collectionstext_generation.generate
Read tutorial →
Audio

Voice-note transcriber

User drops a 5-minute m4a in Slack. Transcribe with Deepgram, summarize with GPT-4o-mini, post back as a thread reply.

speech.transcribetext_generation.generatechat.thread_reply
Read tutorial →
Audio

Text-to-speech podcast snippet

Turn a blog post into a 60-second ElevenLabs audio teaser. Cache the MP3, post to your CDN.

text_generation.generatespeech.synthesize
Read tutorial →
AI

Structured extraction over invoices

Hand a raw invoice text blob to text_generation.extract_structured with a JSON schema; get back typed line items. No prompt-engineering ritual.

text_generation.extract_structured
Read tutorial →
AI

Embeddings for semantic search

Roll your own search if knowledge.* is too opinionated: compute embeddings, store vectors in your DB, do cosine yourself.

embedding.create
Read tutorial →
Scraping

Browser automation: flight prices

Spin up a headless browser, hit Google Flights, extract structured pricing. Screenshot for the audit trail.

browser.runbrowser.extractbrowser.screenshot
Read tutorial →
Web

URL summarizer

User pastes a link. Fetch the page (web.fetch_url), summarize with GPT-4o-mini, return 3 bullets. Cheaper than browser.* when the page is static.

web.fetch_urltext_generation.generate
Read tutorial →
Web

Topic news watcher

Every hour, hit web_search.news for your topics. Diff against last run. New items go to Slack.

web_search.newschat.send
Read tutorial →
Data

CSV importer with auto-types

User uploads a messy CSV. data.csv_parse infers types, returns rows. Then extract_structured normalizes column names.

data.csv_parsetext_generation.extract_structured
Read tutorial →
Infra

Webhook fan-out

One event in, N webhooks out. The platform handles retries and records every delivery in /audit-logs.

notification.webhook
Read tutorial →
Docs

PDF to structured data

Run document.parse (LlamaParse) on a vendor invoice PDF, then extract_structured to typed JSON. End-to-end in two calls.

document.parsetext_generation.extract_structured
Read tutorial →
Media

Auto-subtitle a video

Upload an mp4, get an SRT back. video.subtitle uses Deepgram under the hood - burn-in is your codec, not ours.

video.subtitle
Read tutorial →
Calendar

Calendar scheduler

Agent reads your free/busy, proposes 3 slots, creates the event when the invitee picks one. Nylas under the hood.

calendar.readcalendar.createcalendar.updatecalendar.cancelscheduling.send_invite
Read tutorial →
CRM

CRM auto-enrich

Read a HubSpot contact, search the web, write enriched fields back to the same record. crm.read + crm.write handle the auth.

crm.readweb_search.querycrm.write
Read tutorial →
Ticketing

Jira ticket from Sentry error

Sentry webhook fires, summarize the stack trace, ticket.create in Jira/Linear, then ticket.update with the deploy SHA once linked.

text_generation.generateticket.createticket.update
Read tutorial →
Email

Email triage from a real inbox

email.search for unreads, email.read to fetch bodies, GPT-4o-mini classifies + drafts, email.reply or email.draft based on confidence.

email.searchemail.readtext_generation.generateemail.replyemail.draft
Read tutorial →
Internal

Slack DM digest

Once a day, chat.read each Slack channel you care about, summarize unreads, chat.dm a personalized digest to each opted-in user.

chat.readtext_generation.generatechat.dm
Read tutorial →
Platform

Publish a private API to your Upivia

Wrap your internal HTTP API (or a third-party one you have a key for) as a service that only your org can call. Agents dispatch through /v1/service-requests like any built-in.

custom.*
Read tutorial →
Vendors

Publish a service into the public catalog

You run an API and want every Upivia customer to be able to enable it. Submit an adapter spec, we wire it up, and you become a line item in /admin/services.

custom.*
Read tutorial →
Click any card for a full walkthrough. All nine share the same Bearer token, balance, and audit trail.
Bearer tokens

Authentication

Every agent gets a single API key on creation. Pass it as a Bearer token in the Authorization header. We store only an scrypt hash — we cannot recover lost keys, so save it when you create the agent.

bash
# Pass the agent key as a Bearer token on every request.
Authorization: Bearer aw_live_xxxxxxxxxxxxxxxxxxxxxxxxx
Treat agent keys like database passwords. Never commit them to git. Use environment variables or a secret manager.
POST /v1/agents

Create an agent

Create programmatically:

bash
# Create an agent. The response includes the API key exactly once -
# store it somewhere safe (env var, secret manager) before closing the tab.
curl -X POST https://www.upivia.com/v1/agents \
  -H "Content-Type: application/json" \
  --cookie "authjs.session-token=..." \
  -d '{
    "name": "support-bot",
    "description": "Handles inbound customer email."
  }'
json
{
  "agent": {
    "id": "agt_01HQ...",
    "name": "support-bot",
    "status": "active",
    "created_at": "2026-05-20T..."
  },
  "api_key": "aw_live_xxxxxxxxxxxxx"
}

Or use the dashboard at /agents — click New agent, fill the form, and copy the key from the success modal.

POST /v1/agents/:id/services

Enable services

By default a new agent has zero permissions. Enable a service by creating a binding with a monthly cap, daily limit, and optional approval threshold.

bash
# Bind a service to an agent with a monthly cap, daily limit,
# optional approval threshold, and per-service configuration.
curl -X POST https://www.upivia.com/v1/agents/agt_01HQ.../services \
  -H "Content-Type: application/json" \
  --cookie "authjs.session-token=..." \
  -d '{
    "service_id": "svc_email",
    "monthly_budget_cents": 5000,
    "daily_request_limit": 100,
    "approval_threshold_cents": 500,
    "configuration": {
      "allowed_domains": ["example.com"]
    }
  }'

What each field means:

monthly_budget_centsHard cap on this agent's spend per operation per month. Wallet still needs funds.
daily_request_limitMax number of calls this agent can make per UTC day.
approval_threshold_centsAny single call costing more than this is queued for human approval. 0 disables.
configurationPer-service config. e.g. allowed_domains for email.

Disable a service (idempotent):

bash
# Disable a service binding. Idempotent - safe to call repeatedly.
curl -X DELETE "https://www.upivia.com/v1/agents/agt_01HQ.../services?service_id=svc_email"
How money flows

Budgets & policies

Upivia has one balance per organization (your wallet) and many caps per agent×operation. Every service request runs three checks in order:

  1. Policy check — agent must have anAgentServiceBinding for the requested operation.
  2. Budget check — this month's spend on this operation plus the estimated cost must stay undermonthly_budget_cents; daily count underdaily_request_limit.
  3. Approval gate — if the estimated cost exceeds approval_threshold_cents, the request is parked with status approval_required until a human approves it at /approvals.

Only after all three pass does the wallet get debited and the provider call go out. If the wallet itself is empty, the request fails with insufficient_balance regardless of the per-operation cap.

Budgets are caps, not deposits. Setting a $50 cap doesn't reserve $50 — it just stops spending once $50 has been spent this month. Multiple agents share the org wallet.
POST /v1/service-requests

Dispatch a request

The single endpoint your agent calls for every paid action. Synchronous: returns the executed result, blocks until the provider responds.

Node / TypeScript

typescript
// Dispatch a text-generation request. The Idempotency-Key dedupes
// retries so a flaky network never double-charges your wallet.
const res = await fetch("https://www.upivia.com/v1/service-requests", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.AGENTWALLET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    operation: "text_generation.generate",
    payload: {
      model: "openai/gpt-4o-mini",
      messages: [{ role: "user", content: "Summarize this PR." }],
    },
  }),
});
const data = await res.json();
if (data.status !== "executed") throw new Error(data.reason_code);
console.log(data.result);

Python

python
# Same call from Python. Use uuid4() for the idempotency key on each
# logical action; reuse the same key across retries of that action.
import os, uuid, requests

r = requests.post(
    "https://www.upivia.com/v1/service-requests",
    headers={
        "Authorization": f"Bearer {os.environ['AGENTWALLET_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "operation": "web_search.search",
        "payload": {"query": "GPT-5 release date"},
    },
    timeout=30,
)
data = r.json()
assert data["status"] == "executed", data["reason_code"]
print(data["result"])
Always send an Idempotency-Key. If your agent retries, we de-duplicate using (agent_id, key) and return the cached response — no double charges.
What you can call

Operations reference

40+ operations across 21 service families. Prices below are the fixed basePriceCents from src/db/seedData.ts; variable-priced operations are flagged. All amounts are debited from your org wallet after the provider returns.

Communication

operationpayload shapedefault price
email.send{ to, subject, body }$0.01
email.read{ folder?, query?, limit? }$0.01
email.search{ query, limit? }$0.01
email.reply{ message_id, body }$0.01
email.draft{ to, subject, body }$0.01
sms.send{ to, body }$0.01
sms.read{ limit? }$0.01
mms.send{ to, body, media_urls[] }$0.02
voice_call.create{ to, from, twiml_url }$0.25
chat.send{ channel, text }$0.01
chat.read{ channel, limit? }$0.01
chat.dm{ user, text }$0.01
chat.thread_reply{ channel, thread_ts, text }$0.01
chat.upload_file{ channel, file_url, title? }$0.01

AI & LLM

operationpayload shapedefault price
text_generation.generate{ model, messages[] }varies by model
text_generation.extract_structured{ model, messages[], schema }varies by model
speech.transcribe{ audio_url, language? }$0.01
speech.synthesize{ text, voice? }$0.01
embedding.create{ model, input }$0.01

Web & data

operationpayload shapedefault price
web_search.search{ query }$0.01
web_search.news{ query, days? }$0.01
web.fetch_url{ url, max_bytes? }$0.01
browser.run{ url, instructions }$0.10
browser.screenshot{ url, viewport? }$0.02
browser.extract{ url, schema }$0.03
knowledge.query{ collection, query, top_k? }$0.01
knowledge.upsert{ collection, documents[] }$0.05
knowledge.delete{ collection, ids[] }$0.01
knowledge.list_collections{}$0.01
data.csv_parse{ url | content, options? }$0.01

Compute & primitives

operationpayload shapedefault price
code.execute{ language, source, stdin? }$0.02
notification.webhook{ url, body, headers? }$0.01

Media & docs

operationpayload shapedefault price
document.parse{ url, options? }$0.01
video.subtitle{ video_url, language? }$0.01

Integrations (OAuth)

operationpayload shapedefault price
calendar.read{ start, end, calendar_id? }$0.01
calendar.create{ title, start, end, attendees? }$0.01
calendar.update{ event_id, patch }$0.01
calendar.cancel{ event_id }$0.01
scheduling.send_invite{ to[], title, start, end }$0.01
crm.read{ object, id | query }$0.01
crm.write{ object, patch }$0.01
ticket.create{ project, title, body }$0.01
ticket.update{ ticket_id, patch }$0.01

Variable-priced operations (text_generation.generate and text_generation.extract_structured) use per-model rates in the PricingTier table. The actual cost is debited after the provider returns token counts. OAuth integration operations additionally require an installed ConnectedApp at /connected-apps.

Human-in-the-loop

Approvals

If a request's estimated cost crosses an agent'sapproval_threshold_cents, you'll get this back:

json
{
  "status": "approval_required",
  "request_id": "req_01HQ...",
  "estimated_cost_cents": 800,
  "approval_id": "apr_01HQ..."
}

Approve or reject at /approvals, or hit the API:

bash
# Approve resumes the original pipeline. Reject marks it blocked.
curl -X POST https://www.upivia.com/v1/approvals/apr_01HQ.../approve
curl -X POST https://www.upivia.com/v1/approvals/apr_01HQ.../reject

Approving re-runs the pipeline. Rejecting marks the requestblocked. Pending approvals expire after 24 hours.

What can go wrong

Error codes

statusreason_codemeaning
blockedpolicy_violationAgent has no binding for this operation.
blockedmonthly_budget_exceededPer-op monthly cap hit.
blockeddaily_limit_exceededPer-op daily request count hit.
blockedinsufficient_balanceOrg wallet is empty or wouldn't cover the call.
approval_requiredapproval_thresholdCost exceeds threshold; awaiting approval.
failedadapter_errorUpstream provider rejected the call. See result.detail.
failedagent_disabledAgent.status is paused or disabled.
Inbound events

Webhooks

The platform consumes webhooks from our billing provider (for wallet top-ups) and from the voice provider (for call status reconciliation). Outbound webhooks to your systems — e.g. "this request executed", "budget hit" — are on the roadmap but not shipped yet.

Want outbound webhooks now? Poll /v1/audit-logs — every state transition is recorded there.