← All documentationContents ↓

API Reference: Constituent Authentication

Verify the identity of a DataRecord 'person' (a constituent) so client apps can access their own data securely. Initiate via email or SMS link/code, verify, refresh, and read the profile.

Overview

A constituent is a person represented by a DataRecord in your team — a member, donor, parent, customer, or any individual the team interacts with. Constituent Authentication lets that person prove they own a particular email address (or phone number stored on their record), then use the resulting access token on constituent-facing surfaces such as the App Store purchase and entitlement endpoints.

This flow is distinct from team-user authentication (which uses Cognito JWTs or API keys). Constituent auth is built for end-user-facing surfaces: a parent portal, a member self-service page, a mobile app for ticketed event attendees. The lookup matches the supplied contact value against email and phone fields on the team's person-type collections.

Endpoints

MethodPathPurpose
POST/v1/constituent-auth/initiateStart verification; sends a code or magic link.
POST/v1/constituent-auth/verifyVerify a one-time code (CODE method) and mint tokens.
GET/v1/constituent-auth/verify?token=...Complete a magic link (LINK method) and mint tokens.
POST/v1/constituent-auth/refreshRotate the access + refresh token pair.
POST/v1/constituent-auth/logoutRevoke the current session.
GET/v1/constituent-auth/meCurrent constituent profile and active sessions.

The Three-Step Flow

  1. Initiate — The client supplies an email or phone number. The server sends a verification email containing a one-time code or a magic link. (Delivery uses the team's default verified email sender; SMS delivery is not yet available, so use email contact values today.)
  2. Verify — For CODE, the client posts the verificationSimplyId + code. For LINK, the emailed link hits the GET verify endpoint with its token. Either path returns access + refresh tokens.
  3. Use the access token — The client passes it as Authorization: Bearer ... on subsequent calls. When it expires, refresh.

Initiate

Triggers the verification email. The response includes a verificationSimplyId the client must hold onto for the CODE verify step, plus expiresAt. To prevent contact enumeration, the response looks identical whether or not the contact matched a record.

TypeScript SDK

const init = await s360.constituentAuth.initiate({
  teamSimplyId: 'TEAM-XXXX-XXXX',
  contactValue: 'visitor@example.com',
  method: 'CODE', // or 'LINK'
});

// Save init.data.verificationSimplyId — you'll need it to verify a CODE.
// init.data.expiresAt is when the code/link stops working.

cURL

curl -s -X POST "https://api.simply360.app/v1/constituent-auth/initiate" \
  -H "Content-Type: application/json" \
  -d '{
    "teamSimplyId": "TEAM-XXXX-XXXX",
    "contactValue": "visitor@example.com",
    "method": "CODE"
  }'

Method: LINK vs CODE

  • LINK — The user receives a magic link they click; the link completes verification via GET /v1/constituent-auth/verify?token=.... Links expire after 2 hours. Best for desktop email-on-same-device flows.
  • CODE — The user receives a 6-digit one-time code they type into your app; your app posts it to the verify endpoint. Codes expire after 15 minutes and allow at most 5 attempts. Best for mobile and cross-device flows.

Verify

For the CODE method, post the verificationSimplyId and the code the user typed. Optional platform and deviceName fields are stored on the resulting session (they show up in the /me session list). Verification is single-use.

TypeScript SDK

const tokens = await s360.constituentAuth.verify({
  verificationSimplyId: init.data.verificationSimplyId,
  code: '123456',
  platform: 'web',
  deviceName: 'Chrome on macOS',
});

// Persist tokens.data.accessToken and tokens.data.refreshToken on the client.
// Fetch the constituent's own DataRecord simplyId from /v1/constituent-auth/me.

cURL

curl -s -X POST "https://api.simply360.app/v1/constituent-auth/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "verificationSimplyId": "VRFY-1234-ABCD",
    "code": "123456"
  }'

For the LINK method, the emailed magic link completes verification itself (GET /v1/constituent-auth/verify?token=...) and returns the same token payload — posting a LINK verification's verificationSimplyId without the emailed token is rejected. Both paths return accessToken, refreshToken, accessTokenExpiresAt, and refreshTokenExpiresAt.

Use the Access Token

The access token identifies one constituent session. Pass it like any other bearer token, but don't combine it with a server API key — the API picks one principal per request:

// Example: list the constituent's own entitlements in a branded mobile app
const response = await fetch(
  'https://api.simply360.app/v1/app-store/entitlements?appStoreAppSimplyId=APPS-1234-ABCD',
  { headers: { Authorization: `Bearer ${accessToken}` } },
);

Constituent tokens work on constituent-facing endpoints (constituent auth itself, App Store purchases/entitlements/downloads, and constituent mobile-app runtime surfaces). They cannot call team-scoped Data Record admin endpoints or enumerate collections.

Refresh

Access tokens expire after 30 minutes; refresh tokens after 90 days. Refreshing rotates both tokens — store the new refresh token as well, because the old one stops working:

const refreshed = await s360.constituentAuth.refresh({
  refreshToken: storedRefreshToken,
});

// Replace BOTH stored tokens:
// refreshed.data.accessToken and refreshed.data.refreshToken.
curl -s -X POST "https://api.simply360.app/v1/constituent-auth/refresh" \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "..." }'

Get the Current Profile

Returns the constituent's DataRecord simplyId and a list of active sessions (devices they're signed in on):

const me = await s360.constituentAuth.me();

console.log(`Constituent record: ${me.data.simplyId}`);
for (const session of me.data.sessions) {
  console.log(`  ${session.platform} — ${session.deviceName} — last active ${session.lastActiveAt}`);
}
curl -s "https://api.simply360.app/v1/constituent-auth/me" \
  -H "Authorization: Bearer $CONSTITUENT_ACCESS_TOKEN"

Logout

Revokes the current session (and its refresh token):

await s360.constituentAuth.logout();
curl -s -X POST "https://api.simply360.app/v1/constituent-auth/logout" \
  -H "Authorization: Bearer $CONSTITUENT_ACCESS_TOKEN"

Use Cases

  • Member self-service portal — A members-only area where members log in to update their info, view past purchases, manage subscriptions.
  • Parent portal — Parents log in to see their kids' camp registrations, billing, and forms.
  • Event attendee app — Ticket holders sign in via email + code to view their tickets and check schedules.
  • Donor receipts — Past donors authenticate to download tax receipts for their giving history.

Security Notes

  • Server-side rate limits apply: initiate is limited to 5 requests per 15 minutes per IP, verify to 10 per 15 minutes per IP, and refresh to 30 per hour per token (429 RATE_LIMIT_EXCEEDED). Add client-side debouncing so real users don't hit them.
  • Never store refresh tokens in localStorage on a public page. Use HttpOnly cookies or a secure platform keychain.
  • Codes are single-use and attempt-limited. After 5 failed attempts the verification is invalidated and the user must request a new code.
  • Honor expiresAt in your client UI — 15 minutes for codes, 2 hours for links.
  • Unknown contacts get a decoy response. Initiate always returns a verificationSimplyId, so your UI can safely say "check your email" without confirming whether an account exists.