feat(raisersedge): add Blackbaud Raiser's Edge NXT (SKY API) API module by seanspeaks · Pull Request #227 · friggframework/api-module-library · GitHub
Skip to content

feat(raisersedge): add Blackbaud Raiser's Edge NXT (SKY API) API module - #227

Open
seanspeaks wants to merge 1 commit into
nextfrom
cursor/raisersedge-sky-api-module-69c5
Open

seanspeaks wants to merge 1 commit into
nextfrom
cursor/raisersedge-sky-api-module-69c5

Conversation

@seanspeaks

Copy link
Copy Markdown
Contributor

Summary

Add a Frigg v2 API module at packages/v1-ready/raisersedge (@friggframework/api-module-raisersedge@1.0.0), shaped like packages/v1-ready/fathom/ with HubSpot-style extensions/webhooks/.

Port source: Left Hook–owned Raiser's Edge NXT module (Sean-cleared raisersedge.tar.gz from lefthookhq/aes--frigg @ d6dc4bd…, path backend/src/api-modules/raisersedge) — generic Api + Definition. Client-specific coupling (e.g. AES description text, REDIRECT_URI/raisersedge) stripped. No client mocks/sample data; minimal offline fixtures only.

No prior package existed under packages/ (neither v1-ready nor needs-updating); this is a greenfield add.

What the API is / sources

Blackbaud Raiser's Edge NXT via the SKY API (REST) — constituents, gifts, events, fundraising funds/appeals, and webhook subscriptions. Official OpenAPI is not published as a single downloadable document for the full surface; raisersedge.openapi.yaml is a curated subset verified against the interactive docs.

Source URL
SKY API portal https://developer.blackbaud.com/skyapi
Raiser's Edge NXT https://developer.blackbaud.com/skyapi/products/renxt
Authorization https://developer.blackbaud.com/skyapi/docs/authorization
Auth code (confidential) https://developer.blackbaud.com/skyapi/docs/authorization/auth-code-flow/confidential-application/tutorial
Constituent https://developer.blackbaud.com/skyapi/products/renxt/constituent
Gift https://developer.blackbaud.com/skyapi/products/renxt/gift
Event https://developer.blackbaud.com/skyapi/apis/event
Webhooks https://developer.blackbaud.com/skyapi/docs/webhooks
Webhook tutorial https://developer.blackbaud.com/skyapi/docs/webhooks/tutorial
SKY Add-ins https://developer.blackbaud.com/skyapi/docs/addins/overview
Extension points https://developer.blackbaud.com/skyapi/docs/addins/concepts/extension-points

Auth

Detail Value
Method OAuth 2.0 authorization code
Authorize https://oauth2.sky.blackbaud.com/authorization
Token https://oauth2.sky.blackbaud.com/token
API base https://api.sky.blackbaud.com
Headers Authorization: Bearer …, Bb-Api-Subscription-Key
Redirect dedicated RAISERSEDGE_REDIRECT_URI
Env BLACKBAUD_CONSUMER_KEY, BLACKBAUD_CONSUMER_SECRET, BLACKBAUD_SCOPE, BLACKBAUD_SUBSCRIPTION_KEY
Token exchange omits scope (documented Blackbaud shape)
Refresh single-use rotation; maps refresh_token_expires_in → refreshTokenExpire; persists environment_id
Identity md5(environment_id) as entity/credential externalId

Endpoints covered

Area Methods
Constituents listConstituents, getConstituent, createConstituent, updateConstituent, duplicateSearchConstituents, email/phone/address list/create/update, type/title lists
Gifts listGifts, getGift, createGift, updateGift (sends If-Match: *)
Events listEvents, getEvent, createEvent, updateEvent, participant list/create/update
Fundraising listFunds, listAppeals
Webhooks createSubscription, getSubscription, deleteSubscription

Skipped

  • Gift V2, Opportunity, Prospect, Query, Membership, List V2, NXT Data Integration, Gift Aid/Batch, Consent, Custom Field, Code Table, Standard Reports
  • Fundraising campaigns create/update; fund/appeal get/create/update
  • Official list-all webhook subscriptions endpoint (Create + Get are in the tutorial; Delete is referenced by SubscriptionDeletedEvent — list-all not confirmed on developer.blackbaud.com for this PR)
  • Client-repo mocks / sample payloads

Capability manifest

raisersedge.capabilities.json — capability-manifest/v0.1, version 1.0.0. Objects: constituent, gift, event, fund, appeal, subscription. Every capability method exists on Api (asserted in tests/spec-sync.test.js).

Canonical mappings (ADR-057) — UNVALIDATED

Via extensions.mapping → business@1:

  • mappings/contact.js — Individual constituent → Contact
  • mappings/company.js — Organization constituent → Company

No Deal / support@1 / billing@1 mappings. Gifts, events, funds, and appeals do not fit existing canonical schemas without inventing semantics (gifts ≠ deals). Called out per ADR-057.

Fenestra

Draft included: raisersedge.fenestra.yaml — Blackbaud SKY Add-ins (iframe tiles, page actions, full pages) with @blackbaud/sky-addin-client.

✓ Valid Fenestra document
  Contains: 6 extension points, 1 SDKs

Validated with node fenestra.js validate from branch claude/fenestra-spec-draft-Q367t.

Webhooks extension

extensions/webhooks/ (Tier 3, useDatabase: false):

  • OPTIONS /webhooks — CloudEvents Abuse Protection (WebHook-Allowed-Origin: eventgrid.azure.net)
  • POST /webhooks — optional ?webhookKey= check → RAISERSEDGE_WEBHOOK_RESOLVE → RAISERSEDGE_WEBHOOK
  • Lookup by environment_id (or hashed externalId) / persisted subscription ids

Pattern from HubSpot. Blackbaud does not document an HMAC signature header; shared secret on the webhook URL is the documented pattern.

Proof

npm test (module) — PASS

cd packages/v1-ready/raisersedge && npm test

Test Suites: 5 passed, 5 total
Tests:       44 passed, 44 total

Includes spec-sync, mapping, and webhook extension tests.

Screenshot / terminal capture:

raisersedge npm test pass

Full log: /opt/cursor/artifacts/raisersedge-npm-test.txt
Fenestra: /opt/cursor/artifacts/raisersedge-fenestra-validate.txt
Validate notes: /opt/cursor/artifacts/raisersedge-frigg-validate.txt

frigg validate / definition schema — partial

  • frigg validate on friggframework/frigg branch feat/frigg-validate targets an app Definition (ADR-051), not a standalone api-module. Not run against a Frigg app here.
  • Ran validateApiModuleDefinition() from @friggframework/schemas on that branch:
    • valid: true for a schema-shaped oauth Definition (descriptor form matching packages/schemas/tests)
    • valid: false for as-shipped runtime Definition (modelName, mappings, getAuthorizationRequirements, live functions) — schema lag vs. runtime convention / sibling modules; recorded as an open question.

frigg auth test . — pending credentials

No Blackbaud SKY sandbox OAuth client / RAISERSEDGE_REDIRECT_URI / subscription key in this environment. Do not fake it.

Prior work credited

  • Shape / offline tests / OpenAPI sync: packages/v1-ready/fathom/
  • Webhooks extension pattern: packages/v1-ready/hubspot/extensions/webhooks/
  • Capability / mappings / Fenestra PR shape: Close CRM / monday.com module PRs
  • Left Hook Raiser's Edge port (Sean-cleared tarball): generic Api + Definition (OAuth quirks, refresh rotation, environment_id persistence)
  • ADR-057 / ADR-020: friggframework/frigg docs
  • Fenestra draft spec: claude/fenestra-spec-draft-Q367t

Open questions

  1. Should api-module-definition.schema.json allow modelName, mappings, getAuthorizationRequirements, and live function values (not { type: 'function' } descriptors)?
  2. Confirm official list webhook subscriptions endpoint (if any) and exact DELETE path/response — Create + Get are in the tutorial; Delete is implied by SubscriptionDeletedEvent.
  3. Prefer current oauth2.sky.blackbaud.com/authorization vs. newer SPA authorize URL shown as authorization_spa in docs (clipboard values not extractable from the rendered page)?
  4. Default BLACKBAUD_SCOPE set for marketplace apps (rnxt.r rnxt.w rnxt.d rnxt.whk appears in auth tutorial samples) — leave unset and require env?
  5. Gift → Deal mapping: deliberately skipped; should a future fundraising@ canonical schema cover gifts/appeals/funds?
  6. Webhook delivery envelope field for environment_id across all event types — hosts may need to override extractRoutingHints if some types only carry subscription id.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Port Left Hook-cleared Raiser's Edge NXT module into Frigg v2 shape under
packages/v1-ready/raisersedge: OAuth2 SKY client, definition, OpenAPI,
capability manifest, ADR-057 Contact/Company mappings (UNVALIDATED), SKY
Add-ins Fenestra draft, and HubSpot-style webhook extension.

Co-authored-by: Sean Matthews <sean.matthews@lefthook.co>
@seanspeaks
seanspeaks marked this pull request as ready for review October 22, 2026 00:36
@seanspeaks seanspeaks added the auth-test pending Code and CI green; frigg auth test waits on credentials label Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth-test pending Code and CI green; frigg auth test waits on credentials

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants