thread-master/apps/backend/generate/api/radar.api

652 lines
25 KiB
Plaintext
Raw Permalink Normal View History

2026-08-03 05:52:02 +00:00
syntax = "v1"
// demand-radar: service profile / radar watches / sweeps / opportunities / replies
// spec: docs/product/demand-radar/spec.md §5.2
// 未實作能力一律回 501radarDomain.ErrNotReady禁止 102000 空成功。
type (
// ---------- ServiceProfile ----------
ServiceItem {
Name string `json:"name"`
PriceMin float64 `json:"price_min,optional"`
PriceMax float64 `json:"price_max,optional"`
Currency string `json:"currency,optional"`
}
ServiceCasePublic {
Title string `json:"title"`
Summary string `json:"summary,optional"`
Link string `json:"link,optional"`
}
FaqItem {
Question string `json:"question"`
Answer string `json:"answer"`
}
ServiceProfilePublic {
Exists bool `json:"exists"`
Services []ServiceItem `json:"services"`
Cases []ServiceCasePublic `json:"cases"`
Forbidden []string `json:"forbidden"`
Faq []FaqItem `json:"faq"`
ServiceAreas []string `json:"service_areas"`
RemoteOk bool `json:"remote_ok"`
Availability string `json:"availability,optional"`
ToneNote string `json:"tone_note,optional"`
UpdatedAt int64 `json:"updated_at,optional"`
}
UpsertServiceProfileReq {
Services []ServiceItem `json:"services"`
Cases []ServiceCasePublic `json:"cases,optional"`
Forbidden []string `json:"forbidden,optional"`
Faq []FaqItem `json:"faq,optional"`
ServiceAreas []string `json:"service_areas,optional"`
RemoteOk bool `json:"remote_ok,optional"`
Availability string `json:"availability,optional"`
ToneNote string `json:"tone_note,optional"`
}
// ---------- RadarWatch ----------
RadarWatchPublic {
2026-08-13 02:22:24 +00:00
Id string `json:"id"`
Terms []string `json:"terms"`
ExcludeTerms []string `json:"exclude_terms"`
Regions []string `json:"regions"`
Status string `json:"status"` // active | paused | archived
ContextMode string `json:"context_mode"` // generic | product
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
BrandNameSnapshot string `json:"brand_name_snapshot,optional"`
ProductLabelSnapshot string `json:"product_label_snapshot,optional"`
ContextBoundAt int64 `json:"context_bound_at,optional"`
PauseReason string `json:"pause_reason,optional"`
LastSweptAt int64 `json:"last_swept_at,optional"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
// 只在建立當下這是使用者第一組訂閱、且已排入首巡時為 true不是持久狀態僅供前端顯示一次性提示。
FirstSweepTriggered bool `json:"first_sweep_triggered,optional"`
2026-08-03 05:52:02 +00:00
}
ListWatchesReq {
2026-08-13 02:22:24 +00:00
Page int `form:"page,default=1"`
PageSize int `form:"pageSize,default=20"`
Status string `form:"status,optional"`
ContextMode string `form:"context_mode,optional"`
BrandId string `form:"brand_id,optional"`
ProductId string `form:"product_id,optional"`
2026-08-03 05:52:02 +00:00
}
WatchListData {
List []RadarWatchPublic `json:"list"`
Pagination Pagination `json:"pagination"`
ActiveCount int `json:"active_count"`
MaxActive int `json:"max_active"`
ProfileExists bool `json:"profile_exists"`
}
CreateWatchReq {
Terms []string `json:"terms"`
ExcludeTerms []string `json:"exclude_terms,optional"`
Regions []string `json:"regions,optional"`
Enabled bool `json:"enabled,optional"`
2026-08-13 02:22:24 +00:00
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
2026-08-03 05:52:02 +00:00
}
2026-08-27 05:35:07 +00:00
// Hours are 023 in Asia/Taipei. Empty GET means default [6] (台北 06:00).
// int64go-zero JSON 對 []int 常解不出來,會變成 400001。
RadarSchedulePublic {
Hours []int64 `json:"hours"`
Timezone string `json:"timezone"`
}
PutRadarScheduleReq {
Hours []int64 `json:"hours,optional"`
}
2026-08-03 05:52:02 +00:00
UpdateWatchReq {
Id string `path:"id"`
Terms []string `json:"terms,optional"`
ExcludeTerms []string `json:"exclude_terms,optional"`
Regions []string `json:"regions,optional"`
}
WatchIdReq {
Id string `path:"id"`
}
2026-08-13 02:22:24 +00:00
AssignProductReq {
Id string `path:"id"`
BrandId string `json:"brand_id"`
ProductId string `json:"product_id"`
}
2026-08-03 05:52:02 +00:00
SuggestWatchTermsReq {
2026-08-13 02:22:24 +00:00
Limit int `json:"limit,optional"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
2026-08-03 05:52:02 +00:00
}
WatchTermSuggestion {
2026-08-13 02:22:24 +00:00
Term string `json:"term"`
Reason string `json:"reason"`
Usage string `json:"usage"` // include | exclude
BasisKind string `json:"basis_kind,optional"`
BasisText string `json:"basis_text,optional"`
2026-08-03 05:52:02 +00:00
}
WatchSuggestData {
List []WatchTermSuggestion `json:"list"`
}
TriggerSweepData {
JobId string `json:"job_id"`
SweepId string `json:"sweep_id,optional"`
}
// ---------- Opportunity ----------
OpportunityReason {
Dimension string `json:"dimension"` // authenticity | intent | region | freshness | fit
Score int `json:"score"`
Reason string `json:"reason"`
}
OpportunityOverride {
FromBand string `json:"from_band,optional"`
ToBand string `json:"to_band,optional"`
FromStatus string `json:"from_status,optional"`
ToStatus string `json:"to_status,optional"`
ActorUid int64 `json:"actor_uid"`
At int64 `json:"at"`
}
2026-08-13 02:22:24 +00:00
ProductFitReasonPublic {
Dimension string `json:"dimension"` // pain | scenario | audience | capability
Score int `json:"score"`
Reason string `json:"reason"`
CandidateExcerpt string `json:"candidate_excerpt,optional"`
ProductBasis string `json:"product_basis,optional"`
}
ProductMatchPublic {
BrandId string `json:"brand_id"`
ProductId string `json:"product_id"`
BrandNameSnapshot string `json:"brand_name_snapshot"`
ProductLabelSnapshot string `json:"product_label_snapshot"`
BrandUpdatedAt int64 `json:"brand_updated_at"`
ProductUpdatedAt int64 `json:"product_updated_at"`
ProductFitScore int `json:"product_fit_score"`
ProductFitBand string `json:"product_fit_band"` // strong | possible | weak
Eligible bool `json:"eligible"`
Excluded bool `json:"excluded"`
ExcludeReason string `json:"exclude_reason,optional"`
Reasons []ProductFitReasonPublic `json:"reasons"`
Risks []string `json:"risks"`
WatchIds []string `json:"watch_ids"`
MatchedTerms []string `json:"matched_terms"`
MatchedAt int64 `json:"matched_at"`
}
2026-08-03 05:52:02 +00:00
OpportunityPublic {
Id string `json:"id"`
WatchId string `json:"watch_id,optional"`
Source string `json:"source"` // threads | manual | scout_promote
SourceScoutPostId string `json:"source_scout_post_id,optional"`
ExternalId string `json:"external_id"`
Permalink string `json:"permalink"`
AuthorHandle string `json:"author_handle"`
Text string `json:"text"`
PostedAt int64 `json:"posted_at"`
Status string `json:"status"` // judging | qualified | rejected | accepted | dismissed
IntentScore int `json:"intent_score"`
IntentBand string `json:"intent_band"` // high | mid | low
Reasons []OpportunityReason `json:"reasons"`
RegionDetected string `json:"region_detected,optional"`
RegionMatch string `json:"region_match"` // match | mismatch | unknown
FreshnessHours int `json:"freshness_hours"`
MatchedService string `json:"matched_service,optional"`
MatchedTerms []string `json:"matched_terms"`
RejectReason string `json:"reject_reason,optional"`
Override *OpportunityOverride `json:"override,optional"`
ContactId string `json:"contact_id,optional"`
DefaultReply *ReplyVariantPublic `json:"default_reply,optional"`
2026-08-13 02:22:24 +00:00
PrimaryBrandId string `json:"primary_brand_id,optional"`
PrimaryProductId string `json:"primary_product_id,optional"`
PrimaryBrandName string `json:"primary_brand_name,optional"`
PrimaryProductLabel string `json:"primary_product_label,optional"`
PrimaryProductFitScore int `json:"primary_product_fit_score,optional"`
PrimaryProductFitBand string `json:"primary_product_fit_band,optional"`
PrimaryProductOverridden bool `json:"primary_product_overridden,optional"`
ProductMatches []ProductMatchPublic `json:"product_matches"`
ReviewState string `json:"review_state"` // pending | completed | removed
PreviousReviewState string `json:"previous_review_state,optional"`
RemovalReason string `json:"removal_reason,optional"`
RemovalNote string `json:"removal_note,optional"`
RemovedAt int64 `json:"removed_at,optional"`
RemovedBy int64 `json:"removed_by,optional"`
LastMatchedAt int64 `json:"last_matched_at,optional"`
PriorityScore int `json:"priority_score"`
PriorityBand string `json:"priority_band"` // high | review | low
PainFitScore int `json:"pain_fit_score,optional"`
DemandIntentScore int `json:"demand_intent_score,optional"`
EvidenceQualityScore int `json:"evidence_quality_score,optional"`
FreshnessScore int `json:"freshness_score,optional"`
DemandEvidence []string `json:"demand_evidence,optional"`
DemandInputVersion string `json:"demand_input_version,optional"`
DemandMapVersion int64 `json:"demand_map_version,optional"`
2026-08-03 05:52:02 +00:00
CreatedAt int64 `json:"created_at"`
}
RadarTodayStats {
Total int `json:"total"`
High int `json:"high"`
Mid int `json:"mid"`
Low int `json:"low"`
}
RadarTodayData {
Stats RadarTodayStats `json:"stats"`
High []OpportunityPublic `json:"high"`
Mid []OpportunityPublic `json:"mid"`
Low []OpportunityPublic `json:"low"`
TruncatedCount int `json:"truncated_count"`
LastSweptAt int64 `json:"last_swept_at,optional"`
2026-08-13 02:22:24 +00:00
// EmptyReason: not_swept_yet | all_watches_paused | no_watch | no_profile | sweep_failed | no_hit | no_eligible_product_match
2026-08-03 05:52:02 +00:00
EmptyReason string `json:"empty_reason,optional"`
EmptyHint string `json:"empty_hint,optional"`
}
2026-08-13 02:22:24 +00:00
RadarTodayReq {
BrandId string `form:"brand_id,optional"`
ProductId string `form:"product_id,optional"`
FitBand string `form:"fit_band,optional"`
}
2026-08-03 05:52:02 +00:00
ListOpportunitiesReq {
2026-08-13 02:22:24 +00:00
Page int `form:"page,default=1"`
PageSize int `form:"pageSize,default=20"`
Band string `form:"band,optional"`
Status string `form:"status,optional"`
WatchId string `form:"watch_id,optional"`
BrandId string `form:"brand_id,optional"`
ProductId string `form:"product_id,optional"`
FitBand string `form:"fit_band,optional"`
MatchState string `form:"match_state,optional"`
ReviewState string `form:"review_state,optional"`
TimeScope string `form:"time_scope,optional"` // today | 7d | all
PriorityBand string `form:"priority_band,optional"` // high | review | low
Sort string `form:"sort,optional"`
From int64 `form:"from,optional"`
To int64 `form:"to,optional"`
2026-08-03 05:52:02 +00:00
}
OpportunityListData {
List []OpportunityPublic `json:"list"`
Pagination Pagination `json:"pagination"`
}
OpportunityIdReq {
Id string `path:"id"`
}
AcceptOpportunityReq {
Id string `path:"id"`
}
AcceptOpportunityData {
OpportunityId string `json:"opportunity_id"`
ContactId string `json:"contact_id,optional"`
Status string `json:"status"`
}
DismissOpportunityReq {
Id string `path:"id"`
Reason string `json:"reason,optional"`
}
2026-08-13 02:22:24 +00:00
ReviewStateReq {
Id string `path:"id"`
State string `json:"state"` // pending | completed | removed
RemovalReason string `json:"removal_reason,optional"`
RemovalNote string `json:"removal_note,optional"`
DuplicateOpportunityId string `json:"duplicate_of_opportunity_id,optional"`
}
DemandMapProductReq {
ProductId string `path:"productId"`
}
DemandMapPhrase {
Text string `json:"text"`
Kind string `json:"kind"`
BasisKind string `json:"basis_kind,optional"`
BasisText string `json:"basis_text,optional"`
Origin string `json:"origin"` // product | user | ai
Enabled bool `json:"enabled"`
}
DemandMapPublic {
ProductId string `json:"product_id"`
DemandInputVersion string `json:"demand_input_version"`
MapVersion int64 `json:"map_version"`
State string `json:"state"` // ready | incomplete | stale
PainPhrases []DemandMapPhrase `json:"pain_phrases"`
ScenarioPhrases []DemandMapPhrase `json:"scenario_phrases"`
DesiredOutcomes []DemandMapPhrase `json:"desired_outcomes"`
SolutionSignals []DemandMapPhrase `json:"solution_signals"`
ExclusionSignals []DemandMapPhrase `json:"exclusion_signals"`
SourceBasis []string `json:"source_basis"`
CustomPhrases []DemandMapPhrase `json:"custom_phrases"`
AiEnrichedAt int64 `json:"ai_enriched_at,optional"`
UpdatedAt int64 `json:"updated_at"`
}
UpdateDemandMapReq {
ProductId string `path:"productId"`
ExpectedMapVersion int64 `json:"expected_map_version"`
PainPhrases []DemandMapPhrase `json:"pain_phrases,optional"`
ScenarioPhrases []DemandMapPhrase `json:"scenario_phrases,optional"`
DesiredOutcomes []DemandMapPhrase `json:"desired_outcomes,optional"`
SolutionSignals []DemandMapPhrase `json:"solution_signals,optional"`
ExclusionSignals []DemandMapPhrase `json:"exclusion_signals,optional"`
CustomPhrases []DemandMapPhrase `json:"custom_phrases,optional"`
}
CostPreviewReq {
Action string `form:"action"` // sweep | explore | demand_map_enrich | reply
WatchId string `form:"watch_id,optional"`
ProductId string `form:"product_id,optional"`
CandidateLimit int `form:"candidate_limit,optional"`
}
CostPreviewPublic {
PreviewId string `json:"preview_id"`
Action string `json:"action"`
KeyMode string `json:"key_mode"` // platform | byok
FixedCredits int `json:"fixed_credits"`
MinCredits int `json:"min_credits"`
MaxCredits int `json:"max_credits"`
SearchCalls int `json:"search_calls"`
MaxAiCandidates int `json:"max_ai_candidates"`
RemainingCredits int `json:"remaining_credits"`
EstimateBasis string `json:"estimate_basis"`
ExpiresAt int64 `json:"expires_at"`
}
EnrichDemandMapReq {
ProductId string `path:"productId"`
PreviewId string `json:"preview_id"`
CreditCeiling int `json:"credit_ceiling"`
ExpectedMapVersion int64 `json:"expected_map_version"`
}
2026-08-03 05:52:02 +00:00
OverrideOpportunityReq {
Id string `path:"id"`
Band string `json:"band,optional"`
Status string `json:"status,optional"`
Note string `json:"note,optional"`
}
2026-08-13 02:22:24 +00:00
SetPrimaryProductReq {
Id string `path:"id"`
ProductId string `json:"product_id"`
Reason string `json:"reason"`
}
2026-08-03 05:52:02 +00:00
// ---------- ReplyVariant ----------
ReplyVariantPublic {
Id string `json:"id"`
OpportunityId string `json:"opportunity_id"`
Variant string `json:"variant"` // public_comment | dm | no_sales | professional | humorous
Text string `json:"text"`
UsedAt int64 `json:"used_at,optional"`
SentChannel string `json:"sent_channel,optional"` // outbox | manual_copy
2026-08-09 07:57:35 +00:00
// 只在 sent_channel=outbox 且真的排入既有 Outbox 佇列時才有值。
OutboxId string `json:"outbox_id,optional"`
CreatedAt int64 `json:"created_at"`
2026-08-03 05:52:02 +00:00
}
ListRepliesReq {
Id string `path:"id"`
}
ReplyListData {
List []ReplyVariantPublic `json:"list"`
}
CreateReplyReq {
Id string `path:"id"`
Variant string `json:"variant"`
}
// MarkReplyUsedReq: channel=outbox|manual_copydm 僅 manual_copy
MarkReplyUsedReq {
Id string `path:"id"`
ReplyId string `path:"replyId"`
Channel string `json:"channel"` // outbox | manual_copy
2026-08-09 07:57:35 +00:00
// channel=outbox 時必填:要用哪個 Threads 帳號送出。
AccountId string `json:"account_id,optional"`
2026-08-03 05:52:02 +00:00
}
MarkReplyUsedData {
Reply ReplyVariantPublic `json:"reply"`
HealthAdvice string `json:"health_advice,optional"`
}
// ---------- RadarSweep ----------
RadarSweepPublic {
2026-08-13 02:22:24 +00:00
Id string `json:"id"`
WatchId string `json:"watch_id"`
JobId string `json:"job_id,optional"`
Path string `json:"path"` // api | crawler
HitCount int `json:"hit_count"`
JudgedCount int `json:"judged_count"`
CreatedCount int `json:"created_count"`
TruncatedCount int `json:"truncated_count"`
MatchEvaluatedCount int `json:"match_evaluated_count"`
MatchMergedCount int `json:"match_merged_count"`
FitRejectedCount int `json:"fit_rejected_count"`
FailedReason string `json:"failed_reason,optional"`
CreditsUsed int `json:"credits_used"`
StartedAt int64 `json:"started_at"`
EndedAt int64 `json:"ended_at,optional"`
DemandInputVersion string `json:"demand_input_version,optional"`
DemandMapVersion int64 `json:"demand_map_version,optional"`
DedupedCount int `json:"deduped_count"`
PrefilterPassCount int `json:"prefilter_pass_count"`
PrefilterReviewCount int `json:"prefilter_review_count"`
PrefilterRejectedCount int `json:"prefilter_rejected_count"`
CachedJudgmentCount int `json:"cached_judgment_count"`
TombstoneMatchedCount int `json:"tombstone_matched_count"`
BudgetDeferredCount int `json:"budget_deferred_count"`
SweepStatus string `json:"status"` // complete | partial_budget | blocked_budget | failed
CreditSearch int `json:"credit_search"`
CreditDemandMap int `json:"credit_demand_map"`
CreditJudge int `json:"credit_judge"`
CreditReply int `json:"credit_reply"`
2026-08-03 05:52:02 +00:00
}
ListSweepsReq {
Page int `form:"page,default=1"`
PageSize int `form:"pageSize,default=20"`
WatchId string `form:"watch_id,optional"`
From int64 `form:"from,optional"`
To int64 `form:"to,optional"`
}
SweepListData {
List []RadarSweepPublic `json:"list"`
Pagination Pagination `json:"pagination"`
}
2026-08-09 07:57:35 +00:00
// ---------- Manual ImportP1不承諾全平台自動抓取的合規補位 ----------
ImportOpportunityItem {
Url string `json:"url"`
// 貼文內文;沒有官方 API爬蟲可讀任意網址判定一律吃使用者貼上的文字。
Text string `json:"text"`
// 缺省時從 threads 網址猜 @handle猜不到就留空。
Author string `json:"author,optional"`
// unix nanoseconds UTC缺省用匯入當下時間新鮮度以匯入時間計
PostedAt int64 `json:"posted_at,optional"`
}
ImportOpportunitiesReq {
2026-08-13 02:22:24 +00:00
Items []ImportOpportunityItem `json:"items"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
2026-08-09 07:57:35 +00:00
}
ImportedOpportunityResult {
Url string `json:"url"`
OpportunityId string `json:"opportunity_id,optional"`
Status string `json:"status"` // qualified | rejected | skipped | failed
IntentBand string `json:"intent_band,optional"`
IntentScore int `json:"intent_score,optional"`
// skippedfailed 時的人話原因qualifiedrejected 時通常留空。
Error string `json:"error,optional"`
}
ImportOpportunitiesData {
Results []ImportedOpportunityResult `json:"results"`
}
// ---------- Explore商機頁立即探索短詞 fan-out → 五問判定) ----------
ExploreOpportunitiesReq {
// 16 組 Threads 短詞(契約 A不合規整組擋下不默默修正。
2026-08-13 02:22:24 +00:00
Terms []string `json:"terms"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
PreviewId string `json:"preview_id,optional"`
CreditCeiling int `json:"credit_ceiling,optional"`
DemandInputVersion string `json:"demand_input_version,optional"`
DemandMapVersion int64 `json:"demand_map_version,optional"`
2026-08-09 07:57:35 +00:00
}
ExploreOpportunitiesData {
SweepId string `json:"sweep_id"`
HitCount int `json:"hit_count"`
JudgedCount int `json:"judged_count"`
CreatedCount int `json:"created_count"`
2026-08-13 02:22:24 +00:00
MatchedCount int `json:"matched_count"`
MergedCount int `json:"merged_count"`
2026-08-09 07:57:35 +00:00
TruncatedCount int `json:"truncated_count"`
CreditsUsed int `json:"credits_used"`
2026-08-13 02:22:24 +00:00
DedupedCount int `json:"deduped_count"`
PrefilterPassCount int `json:"prefilter_pass_count"`
PrefilterReviewCount int `json:"prefilter_review_count"`
PrefilterRejectedCount int `json:"prefilter_rejected_count"`
CachedJudgmentCount int `json:"cached_judgment_count"`
TombstoneMatchedCount int `json:"tombstone_matched_count"`
BudgetDeferredCount int `json:"budget_deferred_count"`
SweepStatus string `json:"status"`
2026-08-09 07:57:35 +00:00
}
2026-08-03 05:52:02 +00:00
)
@server (
group: radar
prefix: /api/v1/radar
middleware: AuthJWT
)
service gateway {
@handler GetServiceProfile
get /service-profile returns (ServiceProfilePublic)
@handler UpsertServiceProfile
put /service-profile (UpsertServiceProfileReq) returns (ServiceProfilePublic)
2026-08-27 05:35:07 +00:00
@handler GetRadarSchedule
get /schedule returns (RadarSchedulePublic)
@handler PutRadarSchedule
put /schedule (PutRadarScheduleReq) returns (RadarSchedulePublic)
2026-08-03 05:52:02 +00:00
@handler ListWatches
get /watches (ListWatchesReq) returns (WatchListData)
@handler CreateWatch
post /watches (CreateWatchReq) returns (RadarWatchPublic)
@handler SuggestWatchTerms
post /watches/suggest (SuggestWatchTermsReq) returns (WatchSuggestData)
2026-08-13 02:22:24 +00:00
@handler AssignWatchProduct
post /watches/:id/assign-product (AssignProductReq) returns (RadarWatchPublic)
2026-08-03 05:52:02 +00:00
@handler GetWatch
get /watches/:id (WatchIdReq) returns (RadarWatchPublic)
@handler UpdateWatch
put /watches/:id (UpdateWatchReq) returns (RadarWatchPublic)
@handler ArchiveWatch
delete /watches/:id (WatchIdReq) returns (OkData)
2026-08-13 02:22:24 +00:00
@handler DeleteArchivedWatch
delete /watches/:id/purge (WatchIdReq) returns (OkData)
2026-08-03 05:52:02 +00:00
@handler PauseWatch
post /watches/:id/pause (WatchIdReq) returns (RadarWatchPublic)
@handler ResumeWatch
post /watches/:id/resume (WatchIdReq) returns (RadarWatchPublic)
@handler TriggerWatchSweep
post /watches/:id/sweep (WatchIdReq) returns (TriggerSweepData)
@handler GetRadarToday
2026-08-13 02:22:24 +00:00
get /today (RadarTodayReq) returns (RadarTodayData)
2026-08-03 05:52:02 +00:00
@handler ListOpportunities
get /opportunities (ListOpportunitiesReq) returns (OpportunityListData)
@handler GetOpportunity
get /opportunities/:id (OpportunityIdReq) returns (OpportunityPublic)
@handler AcceptOpportunity
post /opportunities/:id/accept (AcceptOpportunityReq) returns (AcceptOpportunityData)
@handler DismissOpportunity
post /opportunities/:id/dismiss (DismissOpportunityReq) returns (OpportunityPublic)
2026-08-13 02:22:24 +00:00
@handler UpdateOpportunityReviewState
put /opportunities/:id/review-state (ReviewStateReq) returns (OpportunityPublic)
2026-08-03 05:52:02 +00:00
@handler OverrideOpportunity
post /opportunities/:id/override (OverrideOpportunityReq) returns (OpportunityPublic)
2026-08-13 02:22:24 +00:00
@handler SetPrimaryProduct
put /opportunities/:id/primary-product (SetPrimaryProductReq) returns (OpportunityPublic)
@handler GetDemandMap
get /products/:productId/demand-map (DemandMapProductReq) returns (DemandMapPublic)
@handler UpdateDemandMap
put /products/:productId/demand-map (UpdateDemandMapReq) returns (DemandMapPublic)
@handler EnrichDemandMap
post /products/:productId/demand-map/enrich (EnrichDemandMapReq) returns (DemandMapPublic)
@handler GetRadarCostPreview
get /cost-preview (CostPreviewReq) returns (CostPreviewPublic)
2026-08-03 05:52:02 +00:00
@handler ListOpportunityReplies
get /opportunities/:id/replies (ListRepliesReq) returns (ReplyListData)
@handler CreateOpportunityReply
post /opportunities/:id/replies (CreateReplyReq) returns (ReplyVariantPublic)
@handler MarkOpportunityReplyUsed
post /opportunities/:id/replies/:replyId/mark-used (MarkReplyUsedReq) returns (MarkReplyUsedData)
@handler ListSweeps
get /sweeps (ListSweepsReq) returns (SweepListData)
2026-08-09 07:57:35 +00:00
@handler ImportOpportunities
post /import (ImportOpportunitiesReq) returns (ImportOpportunitiesData)
@handler ExploreOpportunities
post /explore (ExploreOpportunitiesReq) returns (ExploreOpportunitiesData)
2026-08-03 05:52:02 +00:00
}