What Talarius does
Send app alerts to a paired phone or live browser display.
Talarius sends short app alerts to a paired iPhone or Android phone or a live browser display. It is for solo developers and small teams running 1–10 low-volume applications. It is not a full observability suite.
There are two ways to send an alert:
- Notifications — your code tells Talarius that something needs attention.
- Heartbeats — a service checks in while healthy; Talarius alerts when check-ins stop or resume.
The production Go API supports browser, APNs, and FCM notification delivery, heartbeat monitoring, reveal-once API keys, iOS and Android pairing, metadata-only usage history, and Stripe-hosted prepaid credit purchases.
API base URL
https://talarius.io/apiNext steps
Sign in, create a project, copy its reveal-once initial API key, then pair an iOS or Android phone or open a browser display.
Quickstart
Send your first alert in under two minutes.
You'll create a project, choose where the alert should appear, and send one notification.
1. Create a project
From the dashboard, create a project. You receive a reveal-once API key named Initial. Copy it now — it is shown only once.
2. Add a receiver
Pair an iOS or Android phone with the 15-minute code from the project’s Devices panel. For the fastest first test, create a named browser display instead, copy its reveal-once URL, and open it in the browser you will leave on screen. Keep codes and display URLs private.
3. Send a notification
curl -X POST https://talarius.io/api/v1/notify \
-H "Authorization: Bearer $TALARIUS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: deploy-482" \
-d '{"title":"Deployment failed","body":"Production exited with **status 1**.\n\n[Open logs](https://example.com/logs/42)","bodyFormat":"markdown"}'A successful request returns 202 Accepted with the target count and one credit charged.
During a short dependency interruption, ordinary API requests may wait up to ten seconds for recovery. A 503 response includes Retry-After guidance. Preserve the original Idempotency-Key when safely retrying a notification; do not blindly repeat a mutation with an uncertain outcome.
{
"status": "accepted",
"notificationId": "notification_01JZ8R6GXN8V5D7WQ2Y4K1M3PA",
"targets": 1,
"creditsCharged": 1
}Delivery results
/api/v1/notifications/{notificationID}Use an active API key for the same project to read the queued, processing, completed, failed, or expired state for up to 15 minutes. Results contain target counts and aggregate provider outcomes, never the notification title or body. A provider acceptance is not a device presentation receipt.
One accepted alert costs one credit regardless of whether it reaches one device or ten.
Add Talarius with Codex
Copy a focused prompt and let Codex adapt the integration to your project.
These recipes ask Codex to inspect your existing stack, preserve its conventions, and add Talarius without putting a project key in browser code or making alert delivery part of the critical path.
Alert after a background job fails
Notify only after the job has exhausted its normal retries.
Inspect this repository's background-job and error-handling conventions. Add a small server-side Talarius notification client for https://talarius.io/api/v1/notify and call it only after a job exhausts its existing retries. Use a concise, non-sensitive title and body. Derive a stable Idempotency-Key from the job name and run ID so the same logical failure cannot be billed twice. Treat HTTP 202 as accepted without claiming device delivery. Use TALARIUS_API_KEY only from the server or CI environment. Add the variable to .env.example without a value. Do not expose the API key to browser code, commit it, or log it. Do not include secrets, credentials, personal, confidential, regulated, or other sensitive information in notification payloads or copied logs. Send only the minimum operational context and use non-secret identifiers or access-controlled HTTPS links without embedded credentials. Use a bounded timeout, keep Talarius failures non-fatal to the host workflow, and add focused tests with a mocked HTTP server—never a live key.Alert when a deployment fails
Send one concise alert from the final failure path in CI.
Inspect this repository's CI and deployment workflow. On the final failed-deployment path, send a server-side POST to https://talarius.io/api/v1/notify with a concise project and failure summary. Read TALARIUS_API_KEY from the CI secret store and use the CI run or deployment ID as a stable Idempotency-Key. Do not echo credentials, raw exception dumps, or sensitive response content. Treat HTTP 202 as accepted without claiming device delivery. Use TALARIUS_API_KEY only from the server or CI environment. Add the variable to .env.example without a value. Do not expose the API key to browser code, commit it, or log it. Do not include secrets, credentials, personal, confidential, regulated, or other sensitive information in notification payloads or copied logs. Send only the minimum operational context and use non-secret identifiers or access-controlled HTTPS links without embedded credentials. Use a bounded timeout, keep Talarius failures non-fatal to the host workflow, and add focused tests with a mocked HTTP server—never a live key.Alert when a payment workflow needs attention
Notify when the existing payment workflow reaches a final actionable failure.
Inspect this repository's server-side payment workflow and its existing retry and reconciliation behavior. On the final actionable failure path, send a POST to https://talarius.io/api/v1/notify with a concise title and only a non-secret payment reference or access-controlled HTTPS link. Never include card data, customer details, provider secrets, or raw webhook content. Derive a stable Idempotency-Key from the workflow and non-secret event reference. Treat HTTP 202 as accepted without claiming device delivery. Use TALARIUS_API_KEY only from the server or CI environment. Add the variable to .env.example without a value. Do not expose the API key to browser code, commit it, or log it. Do not include secrets, credentials, personal, confidential, regulated, or other sensitive information in notification payloads or copied logs. Send only the minimum operational context and use non-secret identifiers or access-controlled HTTPS links without embedded credentials. Use a bounded timeout, keep Talarius failures non-fatal to the host workflow, and add focused tests with a mocked HTTP server—never a live key.Add a service heartbeat
Let Talarius detect a missed check-in and later recovery.
Inspect this repository for the existing server-side scheduler, health loop, or worker lifecycle. Add an empty-body POST to https://talarius.io/api/v1/heartbeat once per minute while the service is healthy. Treat HTTP 204 as accepted. Do not send a request body or call the endpoint from browser code. Avoid overlapping heartbeat requests and do not crash or stall the service when Talarius is unavailable. Use TALARIUS_API_KEY only from the server or CI environment. Add the variable to .env.example without a value. Do not expose the API key to browser code, commit it, or log it. Do not include secrets, credentials, personal, confidential, regulated, or other sensitive information in notification payloads or copied logs. Send only the minimum operational context and use non-secret identifiers or access-controlled HTTPS links without embedded credentials. Use a bounded timeout, keep Talarius failures non-fatal to the host workflow, and add focused tests with a mocked HTTP server—never a live key.Authentication
Project-scoped bearer API keys.
Every production notification and heartbeat request authenticates with a project-scoped API key passed as a bearer token.
Authorization: Bearer $TALARIUS_API_KEYKey rules
- Keys are reveal-once — copy the secret when it is created.
- A project allows two active keys.
- Rotation is staged: the old key remains active until you revoke it.
- Revoked key metadata stays visible but cannot authenticate.
Never commit API keys to source control. Store them as environment variables or in a secrets manager.
Authentication errors
| Field | Type | Description |
|---|---|---|
401 | unauthorized | Missing, malformed, invalid, or revoked bearer key. |
402 | payment_required | The account effective balance is zero or negative. |
Notifications
Send an important event to a project's configured devices.
/api/v1/notifyQueues a title-and-body alert for every configured browser display and paired phone. Eligible iOS and Android phones receive independent native-push and foreground SSE attempts; an open stream never suppresses APNs or FCM. Talarius persists service data only for as long as necessary for essential operations. An unstarted delivery expires after its 15-minute processing window, but logical expiry is not a physical-erasure guarantee. Native apps deduplicate by notification ID and store their own local history.
Do not include secrets, credentials, personal, confidential, regulated, or other sensitive information in notification titles or bodies. Send only the minimum operational context and use non-secret identifiers or access-controlled HTTPS links without embedded credentials.
Request body
| Field | Type | Description |
|---|---|---|
title * | string | Required. Up to 100 Unicode characters. |
body * | string | Required. Up to 1,000 Unicode characters. |
bodyFormat | plain_text | markdown | Optional. Defaults to plain_text. |
Idempotency-Key | header | Optional project-scoped retry key. |
Markdown bodies
Set bodyFormat to markdown to format the notification body. Titles are always plain text, and bodies default to literal plain text when bodyFormat is omitted.
- Text — paragraphs, headings, bold, italic, and strikethrough.
- Lists and structure — ordered, unordered, and task lists; blockquotes; and thematic breaks.
- Code and data — inline code, fenced code blocks, and GitHub-Flavored Markdown tables.
- Links — absolute HTTPS links and autolinks.
Raw HTML displays literally. Images never load and show their alt text. Unsupported formatting stays readable, and only absolute HTTPS links with a hostname are interactive. Source Markdown counts toward the 1,000-character and combined 2,400-byte limits.
{
"title": "Deployment failed",
"body": "Production exited with **status 1**.\n\n[Open logs](https://example.com/logs/42)",
"bodyFormat": "markdown"
}Examples
curl -X POST https://talarius.io/api/v1/notify \
-H "Authorization: Bearer $TALARIUS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: deploy-482" \
-d '{"title":"Deployment failed","body":"Production exited with **status 1**.\n\n[Open logs](https://example.com/logs/42)","bodyFormat":"markdown"}'const apiKey = process.env.TALARIUS_API_KEY;
if (!apiKey) throw new Error('TALARIUS_API_KEY is required');
const response = await fetch('https://talarius.io/api/v1/notify', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + apiKey,
'Content-Type': 'application/json',
'Idempotency-Key': 'deploy-482'
},
body: JSON.stringify({
title: 'Deployment failed',
body: 'Production exited with **status 1**.\n\n[Open logs](https://example.com/logs/42)',
bodyFormat: 'markdown'
}),
signal: AbortSignal.timeout(10_000)
});
if (response.status !== 202) {
throw new Error('Talarius returned ' + response.status);
}import os
import requests
response = requests.post(
'https://talarius.io/api/v1/notify',
headers={
'Authorization': f"Bearer {os.environ['TALARIUS_API_KEY']}",
'Idempotency-Key': 'deploy-482',
},
json={
'title': 'Deployment failed',
'body': 'Production exited with **status 1**.\n\n[Open logs](https://example.com/logs/42)',
'bodyFormat': 'markdown',
},
timeout=10,
)
if response.status_code != 202:
raise RuntimeError(f'Talarius returned {response.status_code}')package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"time"
)
func main() {
if err := notifyTalarius(context.Background()); err != nil {
log.Fatal(err)
}
}
func notifyTalarius(ctx context.Context) error {
apiKey := os.Getenv("TALARIUS_API_KEY")
if apiKey == "" {
return errors.New("TALARIUS_API_KEY is required")
}
payload, err := json.Marshal(map[string]string{
"title": "Deployment failed",
"body": "Production exited with **status 1**.\n\n[Open logs](https://example.com/logs/42)",
"bodyFormat": "markdown",
})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(ctx, 10 * time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://talarius.io/api/v1/notify", bytes.NewReader(payload))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "deploy-482")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("Talarius returned %s", resp.Status)
}
return nil
}Idempotency and limits
For 24 hours, a matching replay returns the original 202 response without charging again. Reusing the same key for another payload returns 409. Per-project rate limits reject excess alerts with 429 and do not bill them. A project with no configured devices returns 409 without charge; once a device is configured, an alert is accepted and charged even if every display is offline.
Heartbeats
Detect a missed application check-in and notify again when it recovers.
/api/v1/heartbeatSend one empty-body check-in per minute from a job or service. The first accepted ping activates monitoring. A missed timeout creates one outage alert; the next accepted ping creates one recovery alert.
curl -X POST https://talarius.io/api/v1/heartbeat \
-H "Authorization: Bearer $TALARIUS_API_KEY"An accepted ping returns 204 and is free. It requires a positive account balance and at least one configured browser or iOS device. Timeout and recovery alerts each cost one credit.
Timeouts and states
| Field | Type | Description |
|---|---|---|
inactive | state | No accepted check-in has activated monitoring yet. |
healthy | state | The latest accepted check-in is within the configured timeout. |
down | state | A deadline was missed and one outage event was generated. |
Ten pings may be accepted in a rolling minute. The eleventh starts a fixed one-hour penalty with one accepted ping per rolling minute; rejected pings do not refresh liveness.
Devices
Use a browser display or pair an iOS or Android phone for focused alerts.
A project supports up to ten active devices shared across browser displays and paired phones. Each browser display is a separate page with one current display session.
Secret links
- The full URL is revealed only when a device is created or its link is rotated.
- Its secret stays in the URL fragment, is scrubbed from the address bar, and is exchanged for a scoped HTTP-only session cookie.
- The link is reusable until rotation or revocation; opening it replaces the previous display session for that device.
- The authenticated `/api/projects/{id}/devices/events` stream is a metadata-free SSE invalidation stream. Clients reload device lists after `ready` or `devices-changed`; events are not replayed.
Delivery model
Delivery uses a live-only Server-Sent Events stream with no replay. The page shows the latest alert prominently and keeps up to 50 newer-first alerts in memory for the current browser session; reload clears that feed.
The dashboard reports current connected/offline state and last-seen time. Rotating or revoking a link immediately terminates the open display.
Paired iPhones
- Create a reveal-once eight-character code from the project Devices panel; it expires after 15 minutes.
- Enter the code in the iOS 17 app. Pairing consumes the code and creates a device-only credential stored in Keychain.
- While the app is active, one foreground SSE connection covers every active project pairing. It is live-only with no replay, reconnects with bounded backoff, and is not reported as online presence.
- The app opens `/api/mobile/installations/events` with its installation bearer; a newer connection replaces the older one and events are never replayed.
- APNs and the live stream are two transports for one logical iPhone. When notification permission is denied, APNs is skipped but the foreground monitor remains available.
- The app's user-visible notification history and read state stay on that iPhone. Unpairing retains local history while stopping future delivery.
- A project supports ten active devices shared across browser displays and phones.
Paired Android phones
Install the Talarius Android app on an Android 10 or newer phone with Google Play services, generate a 15-minute pairing code in the project Devices panel, and enter it manually. Pairing creates no Talarius account or purchase flow in the app.
FCM and the live stream are two transports for one logical Android phone. Notification permission controls title-only background system alerts; denial does not block pairing or foreground monitoring while Talarius is open. Local history remains on that phone until the person deletes it.
Credits & billing
Prepaid, account-wide notification usage.
Credits share one account wallet while every notification debit records its consuming project.
Welcome credits
New accounts receive 100 credits automatically. The grant is applied once and expires after one calendar year.
Buy credit packs
Buy 1,000 credits for $1 USD or 10,000 credits for $5 USD through Stripe Checkout. These are one-time, tax-inclusive purchases, and each pack expires after one calendar year.
Track usage
Usage returns searchable account and project metadata. Notification titles and bodies are not part of usage records or responses.
How charging works
- One accepted alert equals one credit regardless of target count.
- Configured devices count as targets even when their display pages are offline.
- Matching idempotent retries are never charged twice.
- Credits nearest expiration are used first, and nonzero expirations appear in Usage.
- Rejected validation, no-device, authentication, and rate-limit requests are not billed.
- The fixed dashboard test alert is free, limited to one per project every ten seconds, and creates no ledger entry.
- Its short-lived delivery result distinguishes none, skipped, disabled, accepted, partial, and failed native outcomes. mobileDelivery retains the iOS/APNs counters; androidDelivery separately exposes targets, fcmEligible, fcmSkipped, accepted, failed, and liveTargeted. Provider acceptance and stream enqueue are not phone presentation receipts.
Checkout is hosted by Stripe. Talarius grants credits only from verified provider events; refunds and disputes reverse proportional credits and can leave a negative effective balance.
Errors & rate limits
The stable API error contract.
Errors return a JSON envelope with an HTTP status and machine-readable code.
{
"error": {
"code": "rate_limited",
"message": "Project notification rate limit exceeded"
}
}Status codes
| Field | Type | Description |
|---|---|---|
202 | accepted | Notification accepted for delivery. |
204 | no_content | Heartbeat or deletion accepted. |
400 | bad_request | Invalid JSON, input, or body shape. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The effective prepaid balance is zero or negative. |
409 | conflict | Idempotency, missing-device, or capacity conflict. |
429 | rate_limited | Configured project or free-test limit exceeded; not billed. |