Guides

Guides

The practical details behind the endpoints: making retries safe, what you can send, how errors are shaped, and how to trust a webhook.

Idempotency

Networks fail ambiguously. You send a file, the connection drops before our 202 gets back, and you can't tell whether we received it. The safe move is to retry — but a naive retry would duplicate the elder's data.

Send an Idempotency-Key header (a UUID you generate, one per distinct file) on POST /v1/files. If we've already seen that key, we return the same receipt instead of processing again, so the file lands exactly once no matter how many times you retry.

curl https://api.elderella.com/v1/files \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: 7c9e6a1f-3b2d-4a5e-8f10-2b3c4d5e6f70" \
  -F elder=happy.sun.meadow@care.elderella.com \
  -F file=@./lab-result.pdf

File types, size, and purpose

The API takes one file per POST /v1/files. Both limits are validated before anything is stored.

Accepted types (v1)

KindTypes
DocumentsPDF, plain text (.txt)
ImagesJPEG, PNG, HEIC
AudioWAV, MP3, M4A

An unsupported type is a 415 unsupported_media_type. New types may be added within v1 — that's an additive change, so it won't break you.

Size

Maximum 25 MB per file. Larger is a 413 file_too_large. If your payloads run bigger, tell us — the limit is a launch default, not a permanent ceiling.

The purpose hint

purpose is an optional free-text hint about what a file is — lab_result, discharge_summary, pharmacy fill. It helps us route the file without having to read it first. It is never validated: an unrecognized value is a hint we don't act on yet, not an error, so you can always send one. These are suggested values, not a fixed list:

Suggested purposeFor
lab_resultLab and diagnostic reports
discharge_summaryHospital or facility discharge paperwork
medication_listA current medication list or fill record
care_noteA free-text note about the elder's care

Error handling

Every /v1 error uses one envelope. Branch on code (stable, part of the versioned contract); treat message as human-readable text that may change.

{
  "error": {
    "type": "invalid_request_error",
    "code": "file_too_large",
    "message": "The file exceeds the 25 MB limit.",
    "param": "file"
  }
}
StatusCodeMeans
400invalid_requestA field is missing or malformed, or the address shape is wrong.
401unauthorizedThe bearer token is missing, malformed, expired, or revoked.
403insufficient_scopeA valid token that lacks the scope this route needs.
403no_active_connectionThe single no-oracle send error — unresolvable address, unconsented elder, and revoked connection all return this, identically.
404not_foundThe id doesn't name a resource you can see (identical whether unknown or owned by another partner).
409idempotency_key_reuseAn Idempotency-Key was reused with a different payload.
413file_too_largeThe file is over 25 MB.
415unsupported_media_typeThe file's type isn't in the v1 accepted set.
429rate_limitedYou exceeded the endpoint's rate limit. Honour the Retry-After header.
The token endpoint speaks OAuth's error shape, not this one. POST /oauth/token returns the RFC 6749 form — { "error": "invalid_client" } — with values invalid_request, unsupported_grant_type, invalid_client, or invalid_scope. Every credential failure collapses to invalid_client. See authentication.

Don't try to tell no-oracle cases apart. The identical 403 no_active_connection and the identical 404 not_found are contract guarantees. Handle each as its single meaning — "no live connection here," "no resource you can see" — and don't infer more.

Verifying webhook signatures

Every webhook delivery carries an Elderella-Signature header. Always verify it before trusting the payload — it proves the event came from us and lets you reject replays.

Header
Elderella-Signature: t=1753716000,v1=5f2a...c9

The signature is an HMAC-SHA256 over the string "{t}.{raw_body}" — the timestamp, a dot, then the exact raw request body — keyed with the per-partner signing secret we issue alongside your credentials. To verify:

  1. Read t and v1 from the header.
  2. Compute HMAC_SHA256(secret, "{t}." + raw_body) over the raw body, before any JSON parsing.
  3. Compare it to v1 with a constant-time equality check.
  4. Reject the event if t is too old (e.g. more than 5 minutes) to stop replays.
Python
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, sig = parts["t"], parts["v1"]
    if abs(time.time() - int(t)) > tolerance:
        return False  # too old — reject as a possible replay
    signed = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
Node.js
const crypto = require("crypto");

function verify(rawBody, header, secret, tolerance = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const { t, v1 } = parts;
  if (Math.abs(Date.now() / 1000 - Number(t)) > tolerance) return false;
  const signed = `${t}.` + rawBody;                     // rawBody as received
  const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Verify over the raw body. Compute the HMAC on the exact bytes you received, before parsing JSON — re-serializing the parsed object can change whitespace or key order and break the signature. And dedupe on the event id: delivery is at-least-once, so the same event can legitimately arrive twice.

Back to connections & webhooks API reference