Automation API
Let your automations talk to your team
Every Urva Prep business gets bot accounts for free — a stable API key your own scripts, Zapier zaps, n8n workflows, or cron jobs can use to message an employee or post an announcement. Everything lands in the app in real time, just like it came from a person.
Get your API key
Start your team
Sign up as the business owner — no bot key is needed to get going.
Open Settings → Bot accounts
In the dashboard, head to Settings and find the Bot accounts card.
Create a bot, copy its key
Name it (e.g. "Shift Reminder"). The raw key is shown once — stash it somewhere safe.
Make your first call
Use that key as a bearer token against any endpoint below.
Base URL & auth
All endpoints live under one base URL. Authenticate with a bot's key (Messaging/Announcements below) or an automation channel's key (Automations below) as a bearer token — no user login, no token refresh.
Base URL https://backend-production-20c6.up.railway.app Header Authorization: Bearer <your-bot-or-channel-api-key> Header Content-Type: application/json
Embed object reference
Automation channels accept clean, rich embeds — shaped to match Discord's own webhook embed object as closely as reasonable, so a tool that already knows how to post a Discord embed needs little to no adaptation to post here instead. Every field is optional; send whatever your automation has.
Example request body
{
"content": "Build #482 failed",
"username": "CI Bot",
"avatar_url": "https://example.com/ci-bot.png",
"embeds": [
{
"title": "Build failed",
"description": "main branch — 3 tests failing",
"url": "https://ci.example.com/builds/482",
"color": 15158332,
"author": { "name": "example/repo" },
"fields": [
{ "name": "Branch", "value": "main", "inline": true },
{ "name": "Duration", "value": "2m 14s", "inline": true }
],
"footer": { "text": "example CI" },
"timestamp": "2026-03-04T18:22:00Z"
}
]
}How it renders
Build #482 failed
example/repo
Build failed
main branch — 3 tests failing
Branch
main
Duration
2m 14s
example CI · 3/4/2026, 6:22:00 PM
| Field | Type | Notes |
|---|---|---|
| title | string | Up to 256 characters. |
| description | string | Up to 4096 characters. |
| url | string (url) | Makes the title a clickable link. |
| color | integer | Decimal RGB, 0–16777215 (e.g. red = 16711680). |
| timestamp | string (ISO 8601) | Shown in the footer. |
| author.name / .url / .icon_url | string | A small byline above the title. |
| footer.text / .icon_url | string | Small text under everything else. |
| image.url | string (url) | Large image, shown at full width. |
| thumbnail.url | string (url) | Small image, shown top-right. |
| fields | array, up to 25 | Each: { name, value, inline? } — inline fields lay out two per row. |
/api/bot/messagesSend a direct message
Message any employee directly. It lands in their normal Chats tab in real time, exactly like a message from a teammate. Identify the person by whichever you have on hand — their Urva Prep user id, phone number, or email.
Request body
{
"to": { "email": "jane@example.com" },
"body": "Your shift starts at 7am tomorrow."
}Example (curl)
curl -X POST https://backend-production-20c6.up.railway.app/api/bot/messages \
-H "Authorization: Bearer <your-bot-api-key>" \
-H "Content-Type: application/json" \
-d '{"to": {"phoneNumber": "+15551234567"}, "body": "Reminder: timesheet due Friday."}'| Field | Type | Notes |
|---|---|---|
| to.userId | string (uuid) | Provide exactly one of userId, phoneNumber, or email |
| to.phoneNumber | string | Must match the employee's number on file exactly |
| to.email | string | Case-insensitive match |
| body | string | 1–4000 characters |
Returns 201 with the created message. The first message to a given employee starts a direct chat between your bot and them; every message after that reuses the same chat, so it reads as one ongoing conversation. A 404 means no employee matched the to field; a 401 means the key is missing, wrong, or revoked.
/api/bot/announcementsPost an announcement
Post a team-wide announcement — the same result as a manager posting one from the dashboard home feed. Every active employee gets notified, and it shows up in the Announcements feed with likes, comments, and read tracking, attributed to your bot's name.
Request body
{
"title": "Store closed Monday",
"body": "Enjoy the long weekend — we reopen Tuesday at 8am."
}Example (curl)
curl -X POST https://backend-production-20c6.up.railway.app/api/bot/announcements \
-H "Authorization: Bearer <your-bot-api-key>" \
-H "Content-Type: application/json" \
-d '{"title": "Safety refresher", "body": "New lift procedure posted — please review before your next shift."}'| Field | Type | Notes |
|---|---|---|
| title | string | 1–150 characters |
| body | string | 1–4000 characters |
Returns 201 with the created announcement (id, title, body, createdAt, createdBy, and its like/comment counts). It's attributed to the bot's name, so employees see who posted it. A 401 means the key is missing, wrong, or revoked.
/api/automation/messagesPost a message to a channel
Post a message — plain text, one or more rich embeds, or both — to an automation channel. Create a channel in the dashboard's Automations tab to get its API key. Only the employees you've given view access to (plus owner/admin) see it; everyone else never knows it exists.
Request body
{
"content": "Build #482 failed",
"username": "CI Bot",
"embeds": [
{
"title": "Build failed",
"description": "main branch — 3 tests failing",
"color": 15158332,
"fields": [
{ "name": "Branch", "value": "main", "inline": true },
{ "name": "Duration", "value": "2m 14s", "inline": true }
]
}
]
}Example (curl)
curl -X POST https://backend-production-20c6.up.railway.app/api/automation/messages \
-H "Authorization: Bearer <your-channel-api-key>" \
-H "Content-Type: application/json" \
-d '{"content": "Deploy finished ✅", "embeds": [{"title": "urva-prep@2.4.0", "color": 3066993}]}'| Field | Type | Notes |
|---|---|---|
| content | string | Optional, up to 2000 characters. At least one of content or embeds is required. |
| username | string | Optional per-message sender name override, up to 80 characters. |
| avatar_url | string (url) | Optional per-message sender avatar override. |
| embeds | array | Optional, up to 10. See the embed object reference above. |
Returns 201 with the created message. Everyone with access to the channel is notified and sees it live if they have the channel open. A 401 means the key is missing, wrong, or revoked; a 400 means the payload failed validation (e.g. neither content nor embeds provided).
/api/automation/webhooks/:channelId/:tokenPost via a Discord-compatible webhook URL
Same as above, but shaped as a URL instead of a bearer header — literally the same layout as a Discord webhook URL (.../webhooks/{id}/{token}). If a tool's only integration option is "paste a Discord webhook URL," paste this instead and it works unmodified. Find the full URL (with your channel's id and key already filled in) on the channel's settings panel in the dashboard.
Example (curl)
curl -X POST https://backend-production-20c6.up.railway.app/api/automation/webhooks/<channel-id>/<your-channel-api-key> \
-H "Content-Type: application/json" \
-d '{"content": "Deploy finished ✅", "embeds": [{"title": "urva-prep@2.4.0", "color": 3066993}]}'| Field | Type | Notes |
|---|---|---|
| :channelId | string (uuid) | The channel's id, shown in the dashboard's Automations tab. |
| :token | string | The channel's raw API key. |
| content / username / avatar_url / embeds | — | Identical body shape to the bearer-key endpoint above. |
Same response shape as the bearer-key endpoint. A 401 means the key in the URL is wrong/revoked, or doesn't match the :channelId segment.
Ready to connect your automations?
Start your team, then set up a bot in Settings in under a minute.
Start your team