API reference.
One REST API for transactional email, mailing lists, currency, and push notifications. Authenticate with a bearer key and you're sending in minutes.
Introduction
The startupAid API is organized around REST. It accepts JSON request bodies, returns JSON responses, and uses standard HTTP status codes and verbs. The base URL is https://api.startupaid.io.
Everything below is available with an API key. Sending domains and email templates are set up once in your dashboard โ then referenced by address and slug from the API.
Authentication
Every /v1 request requires an API key, passed as a bearer token or the X-API-Key header. Keys are hashed at rest and can be revoked at any time from your dashboard.
Authorization: Bearer sa_live_your_key_here
Endpoints
| Group | Method | Path | Description |
|---|---|---|---|
| POST | /v1/send | Queue and send a transactional email | |
| GET | /v1/messages/:id | Fetch a message's delivery status | |
| Mailing lists | GET | /v1/audiences | List your audiences (mailing lists) |
| Mailing lists | POST | /v1/audiences | Create an audience |
| Mailing lists | PATCH | /v1/audiences/:id | Rename / edit an audience |
| Mailing lists | DELETE | /v1/audiences/:id | Delete an audience and its contacts |
| Contacts | GET | /v1/audiences/:id/contacts | List contacts (search + paging) |
| Contacts | POST | /v1/audiences/:id/contacts | Add a contact |
| Contacts | POST | /v1/audiences/:id/contacts/import | Bulk import contacts |
| Contacts | PATCH | /v1/contacts/:id | Update a contact / subscription |
| Contacts | DELETE | /v1/contacts/:id | Remove a contact |
| Currency | GET | /v1/convert | Convert an amount between any two currencies |
| Currency | GET | /v1/rates/:base | Cross-rates for a base currency |
| Currency | GET | /v1/currencies | List supported fiat & crypto currencies |
| Currency | GET | /v1/currency/status | Rate freshness by asset class |
| Push | POST | /v1/push/apps/:appId/devices | Register an end-user's device token |
| Push | POST | /v1/push/apps/:appId/send | Send a notification (iOS, Android & web) |
| Push | GET | /v1/push/apps/:appId/messages/:id | Per-device delivery breakdown |
| System | GET | /health | Liveness probe (unauthenticated) |
Sending domains
You send from your own domain. Add and verify it once under Domains in the dashboard โ we generate the DKIM records, you publish them at your DNS host, and hit verify. After that, any from address on that domain is accepted. Sends from an unverified domain return 403.
Send an email
POST /v1/send with a from (on a verified domain), to, subject, and an HTML body. Delivery is asynchronous: you get 202 Accepted immediately with a queued message, and we retry with backoff behind the scenes.
curl -X POST https://api.startupaid.io/v1/send \
-H "Authorization: Bearer sa_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@yourdomain.com",
"to": "user@example.com",
"subject": "Welcome aboard",
"body": "<h1>Hi there ๐</h1><p>Thanks for signing up.</p>"
}'
# => 202 Accepted
# { "id": "โฆ", "status": "queued", "to": "user@example.com" }Send with a template
Create reusable templates in the dashboard (with {{variable}} placeholders), then send by template slug and pass variables. Omit subject and body โ they come from the template. Variable values are HTML-escaped for you.
curl -X POST https://api.startupaid.io/v1/send \
-H "Authorization: Bearer sa_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@yourdomain.com",
"to": "user@example.com",
"template": "welcome_onboarding",
"variables": { "name": "Alex", "plan": "Founder" }
}'Idempotency
Send an Idempotency-Key header to safely retry a request without sending twice. A repeated key returns the original queued message (with an Idempotent-Replayed: true response header) instead of sending again.
curl -X POST https://api.startupaid.io/v1/send \
-H "Authorization: Bearer sa_live_xxx" \
-H "Idempotency-Key: order-4192-receipt" \
-H "Content-Type: application/json" \
-d '{ "from": "hello@yourdomain.com", "to": "user@example.com",
"subject": "Receipt", "body": "<p>Thanks!</p>" }'Message status
Poll GET /v1/messages/:id using the id returned by a send. Engagement events (delivered, opened, clicked, bounced, complained) are also visible under Logs in the dashboard.
curl https://api.startupaid.io/v1/messages/MESSAGE_ID \
-H "Authorization: Bearer sa_live_xxx"
# => {
# "id": "โฆ", "to": "user@example.com",
# "status": "delivered", // queued | sending | sent | delivered | failed
# "attempts": 1, "createdAt": "โฆ"
# }Mailing lists
An audience is a mailing list โ a named collection of contacts. Manage them from the API or the dashboard; both share the same data. An email address is unique per audience.
curl -X POST https://api.startupaid.io/v1/audiences \
-H "Authorization: Bearer sa_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "name": "Newsletter", "description": "Weekly product updates" }'
# => { "id": "AUDIENCE_ID", "name": "Newsletter", "contactCount": 0, ... }Contacts & import
Add contacts one at a time or bulk-import them. Each contact takes an email, optional firstName/lastName, and a free-form data JSON object for merge attributes. Import de-dupes and skips invalid rows rather than failing the batch.
curl -X POST https://api.startupaid.io/v1/audiences/AUDIENCE_ID/contacts \
-H "Authorization: Bearer sa_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"firstName": "Ada",
"lastName": "Lovelace",
"data": "{\"plan\": \"pro\"}"
}'
# => { "id": "โฆ", "email": "ada@example.com", "subscribed": true, ... }Unsubscribe
Every contact has an opaque unsubscribe token and a hosted one-click page โ no login required for the recipient. Link to it from your list emails, or flip a contact's subscribed state via the API.
# Flip a contact's subscription state
curl -X PATCH https://api.startupaid.io/v1/contacts/CONTACT_ID \
-H "Authorization: Bearer sa_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "subscribed": false }'Convert currency
GET /v1/convert converts an amount between any two supported currencies โ fiat or crypto. Rates come from our own engine (institutional reference rates for fiat, a live feed for crypto), and every response includes rateAsOf so you know exactly how fresh the rate is (crypto updates every minute; fiat tracks a daily institutional reference).
# Fiat, crypto, or a mix โ from/to any supported code.
curl "https://api.startupaid.io/v1/convert?from=USD&to=NGN&amount=100" \
-H "Authorization: Bearer sa_live_xxx"
# => {
# "from": "USD", "to": "NGN", "amount": 100,
# "result": 137503.95,
# "rateAsOf": "2026-07-09T00:00:00Z" // freshness of THIS pair
# }
# Crypto works the same way:
curl "https://api.startupaid.io/v1/convert?from=BTC&to=USD&amount=1" \
-H "Authorization: Bearer sa_live_xxx"
# => { "result": 63837.0, "rateAsOf": "2026-07-10T08:11:23Z" }
# If you've set a CUSTOM rate on the pair, the response also gives you the
# market rate and your margin โ so fintechs book profit with no extra math:
# => {
# "result": 160000, "rate": 1600, // at your rate
# "marketRate": 1375.04, "marketResult": 137503.95,
# "spread": { "rate": 224.96, "percent": 16.36, "amount": 22496.05 }
# }Rates, currencies & freshness
List everything you can convert with /v1/currencies (fiat + crypto, with names and logos), get a full cross-rate table with /v1/rates/:base, and check per-source freshness with /v1/currency/status. Need NGN or another emerging currency, or your own spread? Pin a custom rate under Currencyin the dashboard and it applies to your account's conversions.
curl "https://api.startupaid.io/v1/currencies" \
-H "Authorization: Bearer sa_live_xxx"
# => { "currencies": [
# { "code": "USD", "type": "fiat" },
# { "code": "NGN", "type": "fiat" },
# { "code": "BTC", "type": "crypto", "name": "Bitcoin", "logo": "https://โฆ" },
# โฆ 100+ total
# ]}Push โ register a device
Push is a relay for your own apps. In the dashboard create an app (you can run many โ a consumer app, a driver app, staging vs prod) and add its provider keys: an FCM service account for Android/web, an APNs .p8for iOS, or a generated VAPID keypair for browsers. Then register each end-user's device token under that app, keyed by userRef โ your own user id. Everything is scoped to /v1/push/apps/:appId. Do this from your backend so the secret key never ships inside the client app.
# Register from YOUR backend so the secret key never ships in the app.
# platform: "fcm" (Android/web) ยท "apns" (iOS) ยท "webpush" (browser)
curl -X POST https://api.startupaid.io/v1/push/apps/APP_ID/devices \
-H "Authorization: Bearer sa_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"userRef": "user_42",
"platform": "fcm",
"token": "DEVICE_TOKEN"
}'
# For web push, "token" is the browser PushSubscription JSON (endpoint + keys).Push โ send a notification
One send reaches iOS, Android, and browsersat once โ we fan out to each platform using that app's credentials and return a per-device result. Target a single user, a list of users, specific tokens, or every device. Attach a link so a tap opens deep in your app or on the web. Tokens a provider rejects are retired automatically. Each notification attempt counts as one unit against your monthly allowance.
# One call reaches iOS, Android, and web โ we fan out to each platform.
curl -X POST https://api.startupaid.io/v1/push/apps/APP_ID/send \
-H "Authorization: Bearer sa_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"target": { "userRef": "user_42" },
"title": "Your order shipped ๐ฆ",
"body": "Arriving Tuesday.",
"link": "acme://orders/8921"
}'
# target: { userRef } | { userRefs: [...] } | { tokens: [...] } | { all: true }
# => { "messageId": "โฆ", "status": "sent", "recipients": 3, "sent": 3, "failed": 0 }Errors
Errors return a JSON body with an error field and a conventional HTTP status code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | The payload failed validation (missing or malformed fields, bad email). |
| 401 | unauthorized | Missing or invalid API key. |
| 403 | forbidden | The from-address isn't a domain you've verified for this account. |
| 404 | not_found | The requested resource does not exist. |
| 409 | conflict | Duplicate โ e.g. a contact with that email already exists in the audience. |
| 429 | rate_limited | You've exceeded your plan's rate limit โ back off and retry. |
| 502 | provider_error | An upstream provider (SMTP or FX) failed; safe to retry. |
Official SDKs
Node.js
npm i @startupaid/nodePython
pip install startupaidGo
go get startupaid.io/goRuby
gem install startupaidNeed a key?
Create an account and generate one in seconds.