DEVELOPERS

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

GroupMethodPathDescription
EmailPOST/v1/sendQueue and send a transactional email
EmailGET/v1/messages/:idFetch a message's delivery status
Mailing listsGET/v1/audiencesList your audiences (mailing lists)
Mailing listsPOST/v1/audiencesCreate an audience
Mailing listsPATCH/v1/audiences/:idRename / edit an audience
Mailing listsDELETE/v1/audiences/:idDelete an audience and its contacts
ContactsGET/v1/audiences/:id/contactsList contacts (search + paging)
ContactsPOST/v1/audiences/:id/contactsAdd a contact
ContactsPOST/v1/audiences/:id/contacts/importBulk import contacts
ContactsPATCH/v1/contacts/:idUpdate a contact / subscription
ContactsDELETE/v1/contacts/:idRemove a contact
CurrencyGET/v1/convertConvert an amount between any two currencies
CurrencyGET/v1/rates/:baseCross-rates for a base currency
CurrencyGET/v1/currenciesList supported fiat & crypto currencies
CurrencyGET/v1/currency/statusRate freshness by asset class
PushPOST/v1/push/apps/:appId/devicesRegister an end-user's device token
PushPOST/v1/push/apps/:appId/sendSend a notification (iOS, Android & web)
PushGET/v1/push/apps/:appId/messages/:idPer-device delivery breakdown
SystemGET/healthLiveness 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.

StatusCodeMeaning
400bad_requestThe payload failed validation (missing or malformed fields, bad email).
401unauthorizedMissing or invalid API key.
403forbiddenThe from-address isn't a domain you've verified for this account.
404not_foundThe requested resource does not exist.
409conflictDuplicate โ€” e.g. a contact with that email already exists in the audience.
429rate_limitedYou've exceeded your plan's rate limit โ€” back off and retry.
502provider_errorAn upstream provider (SMTP or FX) failed; safe to retry.

Official SDKs

javascript

Node.js

npm i @startupaid/node
code

Python

pip install startupaid
terminal

Go

go get startupaid.io/go
diamond

Ruby

gem install startupaid

Need a key?

Create an account and generate one in seconds.

Get your API key