SDK and MCP integration
External-ID members, campaign storefronts, Discord linking, server inventory and member attributes, quest action and completion contracts.
SDK 0.4.0
Both standalone ProjectStorefronts created with MCP create_storefront and campaign storefronts are supported. ModuleStorefront IDs are a separate legacy namespace; use list_features IDs. create_storefront starts inactive: add_storefront_item, then set_feature_active. No Hub widget is required for SDK-only use. For campaign stores, add_storefront_item preserves draft ownership; manage_campaign accepts the root campaign ID and get/save/preview/publish/archive. Publishing applies dashboard readiness rules to all activities. list_features identifies standalone versus campaign and the owning child campaign ID plus owningCampaignId for the root campaign. get_storefront_items accepts storefront (ID or unique name). Negative resource amounts are prices, positive amounts are rewards; requirements carry holding limits, stock carries configured total limits. SDK reads include availableStock and canPurchase after balance and prior purchase checks.
External-ID member APIs
See sdk/member-data: Send member data from your backend. sessions.create and members.upsert accept username, email and project-defined customFields. Phone belongs in customFields.phone. No fields are created silently. Inventory, balances and idempotent consumption remain available through members.inventory, members.balances and members.consume.
Harbor-hosted Discord linking
Create sessions with {externalId,origin,returnUrl}; returnUrl must be a URL on the backend-approved origin, with no fragment or reserved harbor_link_* query parameters. It defaults to the origin root. For an unlinked Discord connect/server-join quest, action.kind is oauth and action.url is a short-lived Harbor link. Open it in the same tab. Harbor obtains Discord consent and returns to returnUrl with harbor_link_receipt (or harbor_link_error on cancellation/failure).
On return, call await client.social.finishFromRedirect() before reading quests, or press Check in the default Quest widget: quests.verify finishes the returned receipt first. Finish requires a current SDK session for the same project, member, and origin. Discord identities already owned by a different member are rejected; existing identities are never silently replaced or merged. The receipt is not a login session. A successful link does not itself prove guild membership. Refresh the quest: a server-join quest exposes its Discord invitation, and Check verifies current membership with Discord before recording completion. Users may need to accept the invitation before Check succeeds.
For explicit connect/reconnect UI use client.social.link('discord', returnUrl), navigate to its url, then finishFromRedirect on return. SDK 0.4.0 provides hosted Discord linking. Other provider OAuth, forms, uploads and wallet actions still use host onAction adapters; do not display a working connect button when the action has no hosted URL and no adapter.
Quest action values and verification
Possible action.kind values: oauth, link, form, projectForm, upload, tiktok, wallet, walletSolana, external, invite, keyphrase, verify, check. action can be null. URLs are optional; only open HTTP(S) URLs. Use action.label when present. oauth with a hosted URL opens Harbor; oauth without one requires an adapter. link/external/invite open their URL then recheck. keyphrase asks for text and passes keyPhrase. form/projectForm/upload/tiktok/wallet/walletSolana require their authenticated adapter. verify/check invoke quests.verify. Unknown future values must render an unsupported-action state.
quests.verify always returns an updated HarborQuest on success, not a boolean and not a reward receipt. Inspect isCompleted and canClaimNow; claim separately. ClickLink records the configured visit. DiscordJoin asks Discord for membership; missing identity returns identity_link_required, non-membership requirements_not_met, and provider failure provider_unavailable. ExternalCompletion is read-only in browser verification: only the trusted host backend records gameplay completion. Other affirmative types run Harbor's configured verifier; unmet requirements return ineligible and unimplemented verifiers return unsupported_action. All other types re-read stored events or live resource state without asserting browser-supplied completion. The returned quest can remain incomplete after a successful read. Disabled, archived, inaccessible or foreign-project quests are unavailable.
questTypeName values (case-sensitive): TwitterConnectAccount, DiscordConnectAccount, SteamConnectAccount, TwitchConnectAccount, EmailConnect, ReferFriend, RewardTrigger, CompleteQuests, TwitterFollow, TwitterRetweet, TwitterLike, TwitterComment, DiscordJoin, DiscordSendMessages, DiscordSendMessagesInChannel, ChallengeCompete, ResourceEarn, WagerWin, Tip, DiscordRoleEarn, BoxOpen, ResourceUse, GetTipped, TwitterQuote, TwitterReply, ResourceHave, FormCompletePaperform, TextInput, ClickLink, Nothing, RobloxBadgeHave, RobloxBadgesOwned, RobloxConnectAccount, EthereumWalletConnect, FormCompleteTallyIO, FormCompleteTypeform, ExternalCompletion, RobloxStarAsset, EpicConnectAccount, TwitchFollow, TwitchWatch, SteamProfilePublic, SteamGameAchievement, SteamGamePlaytime, SteamGameRecentPlaytime, FacebookConnectAccount, FacebookLikePage, FacebookSharePost, FacebookHashtagPost, TikTokConnectAccount, TikTokCreatePost, TikTokDirectPost, YoutubeConnectAccount, YoutubeSubscribe, YoutubeLike, YoutubeComment, SolanaWalletConnect, BeamableConnectAccount, BeamableConnectSteamAccount, ExternalIdentitySelfCreate, SteamGameLibraryCheck, SteamGameWishlistCheck, FormCompleteProjectForm, ImageUpload. These identify configuration, not a promise that an adapter or provider integration is enabled. Hosts should choose buttons from action rather than maintain a separate type-to-button mapping.
Completion, repeats, and errors
Use @harbor/server quests.complete -> POST /external/sdk/quests/complete for an ExternalCompletion quest and an external-ID member, with operationId. Create the member first through members.upsert or sessions.create. It records eligibility only; the member claims separately. The legacy /external/quest/complete endpoint uses a target identity and its existing contract; it is not a substitute for SDK durable operation receipts.
Completion codes: member_not_found (create member first), not_found (unknown/foreign quest), unsupported_action (not ExternalCompletion), outside_campaign_dates, ineligible (audience, lifecycle, group or other eligibility), already_completed (claimed non-repeatable quest), not_repeatable_yet (repeat interval has not reopened), invalid_request, idempotency_conflict. An already-completed but unclaimed quest remains successfully complete. A matching successful operationId always replays its original receipt, including across later repeat cycles; a new legitimate event needs a new operationId.
A Daily group label alone does not make an ExternalCompletion quest reset. Campaign Daily/Refreshing groups currently admit Nothing and ResourceHave actions; ExternalCompletion is not supported in those campaign structures. Campaign Daily cycles reopen after a claimed reward at 00:00 UTC, on the next eligible request. Standalone group display does not add a daily reset to each quest. ExternalCompletion repetition follows its own configured repeat rule and requires a fresh backend event and operationId after reopening; do not promise a daily gameplay reward based only on group type.
Capability discovery and refresh
harbor.capabilities() calls POST /external/sdk/capabilities with the project key. It returns apiVersion, minimumSdkVersion for these features, migrationsReady, and features. Check this before adopting 0.4.0 against an older API; an older deployment may return 404. Migration readiness checks required SDK operation, storefront purchase, custom-field storage tables, and native email provenance. Package version alone is not proof of server deployment.
Quest-claim receipts already list storefronts alongside inventory, quests, questGroups and leaderboards. SDK subscribers refresh these reads, including canPurchase. Backend completion does not change balances and affects only quests and groups. Server-side consumption returns affected categories; the host must refresh mounted browser consumers after server-side mutations (there is no push subscription).
Per-type verification reference
Every successful verify returns an updated HarborQuest. Completion and claim eligibility are separate fields. A successful recheck may remain incomplete. verify/check below is selected from the configured affirm metadata; the response action is authoritative. Provider verifiers require their configured identity and integration.
| questTypeName | action.kind | quests.verify behavior |
|---|---|---|
| TwitterConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| DiscordConnectAccount | oauth | Requires Harbor-linked Discord, then records completion. |
| SteamConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| TwitchConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| EmailConnect | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| ReferFriend | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| RewardTrigger | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| CompleteQuests | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| TwitterFollow | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| TwitterRetweet | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| TwitterLike | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| TwitterComment | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| DiscordJoin | oauth before linking; invite/check after linking | Verifies current Discord guild membership; records completion only on success. |
| DiscordSendMessages | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| DiscordSendMessagesInChannel | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| ChallengeCompete | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| ResourceEarn | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| WagerWin | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| Tip | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| DiscordRoleEarn | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| BoxOpen | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| ResourceUse | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| GetTipped | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| TwitterQuote | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| TwitterReply | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| ResourceHave | check | Re-evaluates current resource ownership. |
| FormCompletePaperform | form | Re-reads recorded submission from the authenticated form adapter. |
| TextInput | keyphrase | Checks supplied keyPhrase; returns updated quest or ineligible. |
| ClickLink | link | Records completion of the configured visit. |
| Nothing | check | Re-reads eligibility; claim can be immediately available. |
| RobloxBadgeHave | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| RobloxBadgesOwned | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| RobloxConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| EthereumWalletConnect | wallet | Re-reads linked wallet state; requires an authenticated adapter. |
| FormCompleteTallyIO | form | Re-reads recorded submission from the authenticated form adapter. |
| FormCompleteTypeform | form | Re-reads recorded submission from the authenticated form adapter. |
| ExternalCompletion | external/check | Re-reads backend-recorded completion; browser cannot complete gameplay. |
| RobloxStarAsset | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| EpicConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| TwitchFollow | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| TwitchWatch | verify/check | Re-reads stored events or evaluated state; does not record a browser assertion. |
| SteamProfilePublic | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| SteamGameAchievement | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| SteamGamePlaytime | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| SteamGameRecentPlaytime | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| FacebookConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| FacebookLikePage | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| FacebookSharePost | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| FacebookHashtagPost | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| TikTokConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| TikTokCreatePost | tiktok | Runs configured verifier after authenticated posting adapter. |
| TikTokDirectPost | tiktok | Runs configured verifier after authenticated posting adapter. |
| YoutubeConnectAccount | oauth | Re-reads linked identity; this provider requires a host adapter. |
| YoutubeSubscribe | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| YoutubeLike | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| YoutubeComment | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| SolanaWalletConnect | walletSolana | Re-reads linked wallet state; requires an authenticated adapter. |
| BeamableConnectAccount | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| BeamableConnectSteamAccount | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| ExternalIdentitySelfCreate | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| SteamGameLibraryCheck | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| SteamGameWishlistCheck | verify/check | Runs configured affirmative verifier; unmet conditions return ineligible, unavailable implementations return unsupported_action. |
| FormCompleteProjectForm | projectForm | Re-reads recorded submission from the authenticated form adapter. |
| ImageUpload | upload | Runs configured verifier after an authenticated upload adapter supplies the submission. |