marketplace-doc
    • Data Ingestion
    • Errors
    • Introduction
    • Loan API & Deduction Lifecycle
    • Getting Started
    • OAuth
    • Webhooks
    • Embedded Journey
    • OpenSylo Marketplace Integration API
      • OAuth 2.0
        • Start OAuth authorization
        • Exchange authorization code or refresh token
        • Revoke a token
        • Discover OAuth capabilities
      • Data Ingestion
        • Submit single merchant data
        • Submit bulk merchant data
        • Poll batch processing status
        • Get merchant credit score
        • Data ingestion health check
      • Sales & Events
        • Submit a sales event
        • Submit a repayment event
        • Submit an account flag
      • Loan API
        • Get active loans for a merchant
        • Get loan status
        • Validate deduction amounts
        • Bulk loan status
      • Inbound Webhooks
        • Repayment events
        • Settlement events
      • Outbound Webhooks
        • loan.approved
        • loan.disbursed
        • loan.repayment_updated
        • loan.nearly_complete
        • loan.completed
        • loan.defaulted
        • merchant.created
        • kyc.submitted
        • kyc.approved
        • kyc.rejected
        • funding_request.created
        • funding_request.fulfilled
        • funding_request.rejected
      • Embedded Journey (Marketplace API)
        • Create (or fetch) a merchant
        • Get merchant status (KYC, credit score, funding requests)
        • Update business KYC information
        • Add directors (bulk)
        • Attach a KYC document
        • Submit KYC for review
        • Submit sales data for credit scoring
        • Create a funding request
        • Mint an embed token for the hosted journey
    • OpenSylo Marketplace API
      • OAuth 2.0
        • Start OAuth authorization
        • Exchange code or refresh token
        • Revoke a token
        • OAuth discovery / client metadata
      • Data Ingestion
        • Submit single merchant data
        • Submit bulk merchant data
        • Get merchant credit score
        • Integration health check
      • Loan API
        • Get active loans for a merchant
        • Get loan status
        • Validate deduction amounts
        • Bulk loan status check
      • Inbound Webhooks
        • Send repayment webhook
        • Send settlement webhook

    Data Ingestion

    Overview

    OpenSylo's data ingestion API allows marketplaces to submit merchant performance data for credit scoring and loan eligibility calculation. When you submit data, OpenSylo processes it through its credit scoring engine and returns a score, risk tier, and lending cap in the response.

    Single Merchant Submission

    Endpoint: POST /api/marketplace/data/merchant

    Required scope: data.share.sales

    Required headers:

    HeaderDescription
    AuthorizationBearer <access_token>
    Content-Typeapplication/json

    Request Structure

    The MerchantDataRequest payload contains eight sections:

    SectionRequiredDescription
    merchantIdentityYesBusiness info, onboarding date, verification status
    salesPerformanceYesGMV (30/90/180 day), order counts, growth rate
    revenueConsistencyYesMonthly variance, zero-sales months, sales streaks
    fulfillmentMetricsYesFulfillment rate, cancellation/refund/dispute rates
    payoutCashFlowYesPayout frequency, average payout value, failed payouts
    platformDependencyYesTop product and top customer concentration
    historicalCreditNoPrevious advances, repayment rate, defaults
    behavioralRiskYesSuspensions, fraud flags, GMV spike rate
    monthlySalesHistoryNo6-month GMV history for loan eligibility (see below)

    curl Example

    curl -X POST https://api.opensylo.com/api/marketplace/data/merchant \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "merchantIdentity": {
          "merchantId": "merch_001",
          "businessName": "Adaeze Fashion Store",
          "businessType": "RETAIL",
          "marketplaceOnboardingDate": "2024-06-15T00:00:00.000Z",
          "verificationStatus": true,
          "location": "Lagos, Nigeria",
          "country": "NG",
          "category": "clothing"
        },
        "salesPerformance": {
          "gmv30Days": 1200000,
          "gmv90Days": 3500000,
          "gmv180Days": 6800000,
          "avgMonthlySales": 1133333,
          "orderCount": 450,
          "avgOrderValue": 15111,
          "salesGrowthRate": 12.5
        },
        "revenueConsistency": {
          "monthlySalesVariance": 15,
          "zeroSalesMonths": 0,
          "longestSalesStreak": 18,
          "revenueConcentration": 25
        },
        "fulfillmentMetrics": {
          "fulfillmentRate": 96,
          "cancellationRate": 2.5,
          "refundRate": 1.8,
          "refundAmount": 21600,
          "disputeRate": 0.5,
          "lateDeliveryRate": 3.0
        },
        "payoutCashFlow": {
          "payoutFrequency": "WEEKLY",
          "avgPayoutValue": 280000,
          "failedPayouts": 0,
          "walletBalanceUsage": 50000,
          "settlementDelays": 0
        },
        "platformDependency": {
          "topProductGmvPercentage": 18,
          "topCustomerPercentage": 8
        },
        "historicalCredit": {
          "previousAdvances": 1,
          "repaymentRate": 100,
          "daysPastDue": 0,
          "hasDefaults": false,
          "earlyRepayments": 1
        },
        "behavioralRisk": {
          "accountSuspensions": 0,
          "policyViolationsSeverity": null,
          "hasFraudFlags": false,
          "hasSuddenGmvSpikes": false,
          "gmvSpikeMonthsPercent": 0,
          "hasLinkedAccounts": false
        },
        "marketplaceId": "your_marketplace_uuid",
        "dataTimestamp": "2026-01-29T10:00:00.000Z",
        "correlationId": "corr_batch_20260129"
      }'
    

    TypeScript Example

    const response = await fetch("https://api.opensylo.com/api/marketplace/data/merchant", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        merchantIdentity: {
          merchantId: "merch_001",
          businessName: "Adaeze Fashion Store",
          businessType: "RETAIL",
          marketplaceOnboardingDate: "2024-06-15T00:00:00.000Z",
          verificationStatus: true,
          location: "Lagos, Nigeria",
          country: "NG",
          category: "clothing",
        },
        salesPerformance: {
          gmv30Days: 1200000,
          gmv90Days: 3500000,
          gmv180Days: 6800000,
          avgMonthlySales: 1133333,
          orderCount: 450,
          avgOrderValue: 15111,
          salesGrowthRate: 12.5,
        },
        revenueConsistency: {
          monthlySalesVariance: 15,
          zeroSalesMonths: 0,
          longestSalesStreak: 18,
          revenueConcentration: 25,
        },
        fulfillmentMetrics: {
          fulfillmentRate: 96,
          cancellationRate: 2.5,
          refundRate: 1.8,
          refundAmount: 21600,
          disputeRate: 0.5,
          lateDeliveryRate: 3.0,
        },
        payoutCashFlow: {
          payoutFrequency: "WEEKLY",
          avgPayoutValue: 280000,
          failedPayouts: 0,
        },
        platformDependency: {
          topProductGmvPercentage: 18,
          topCustomerPercentage: 8,
        },
        behavioralRisk: {
          accountSuspensions: 0,
          hasFraudFlags: false,
          hasSuddenGmvSpikes: false,
          gmvSpikeMonthsPercent: 0,
          hasLinkedAccounts: false,
        },
        marketplaceId: "your_marketplace_uuid",
        dataTimestamp: new Date().toISOString(),
        correlationId: `corr_${Date.now()}`,
      }),
    });
    
    const result = await response.json();
    // result.creditScore contains the scoring result
    // result.loanEligibility is present when monthlySalesHistory was provided
    

    Response

    {
      "success": true,
      "message": "Merchant data processed successfully",
      "transactionId": "txn_uuid",
      "creditScore": {
        "totalScore": 81,
        "riskTier": "A",
        "maxLendingCap": 906666,
        "eligiblePercentage": 80,
        "scoreBreakdown": {
          "salesCount": 35,
          "revenueConsistency": 9,
          "fulfillmentOps": 7,
          "stabilityTenure": 10,
          "riskBehavior": 10,
          "riskBonus": 10
        },
        "lendingImplication": "Highest cap, best pricing",
        "calculatedAt": "2026-01-29T10:00:05.000Z",
        "recommendations": "Excellent credit profile. Consider premium lending products."
      },
      "processedAt": "2026-01-29T10:00:05.000Z",
      "nextRefreshDate": "2026-02-28T10:00:00.000Z"
    }
    

    Monthly Sales History & Loan Eligibility

    When you include the optional monthlySalesHistory array, OpenSylo calculates a loan eligibility result in addition to the credit score. This determines the maximum loan amount the merchant qualifies for based on their recent revenue capacity.

    Requirements

    • Exactly 6 entries, one per month, ordered oldest to newest
    • Each entry contains gmv (monthly gross merchandise value in Naira) and orderCount
    • An optional monthLabel (e.g., "2025-11") for reference

    Example with monthlySalesHistory

    {
      "merchantIdentity": { "..." : "..." },
      "salesPerformance": { "..." : "..." },
      "revenueConsistency": { "..." : "..." },
      "fulfillmentMetrics": { "..." : "..." },
      "payoutCashFlow": { "..." : "..." },
      "platformDependency": { "..." : "..." },
      "behavioralRisk": { "..." : "..." },
      "monthlySalesHistory": [
        { "gmv": 950000,  "orderCount": 380, "monthLabel": "2025-08" },
        { "gmv": 1020000, "orderCount": 410, "monthLabel": "2025-09" },
        { "gmv": 1100000, "orderCount": 430, "monthLabel": "2025-10" },
        { "gmv": 1050000, "orderCount": 420, "monthLabel": "2025-11" },
        { "gmv": 1250000, "orderCount": 460, "monthLabel": "2025-12" },
        { "gmv": 1200000, "orderCount": 450, "monthLabel": "2026-01" }
      ],
      "marketplaceId": "your_marketplace_uuid",
      "dataTimestamp": "2026-01-29T10:00:00.000Z"
    }
    

    LoanEligibilityResult Response

    When monthlySalesHistory is provided, the response includes a loanEligibility object:

    {
      "success": true,
      "creditScore": { "..." : "..." },
      "loanEligibility": {
        "avgMonthlyGmv": 1095000,
        "avgMonthlyOrders": 425,
        "aov": 2576.47,
        "gmvTrendPercent": 26.32,
        "gmvVolatilityCov": 0.0963,
        "monthlyRepaymentCapacity": 273750,
        "totalRepaymentCapacity": 1642500,
        "maxEligibleLoan": 1265384.62,
        "totalRepaymentForMaxLoan": 1645000,
        "monthlyRepaymentForMaxLoan": 274166.67,
        "policyParameters": {
          "repaymentRate": 0.25,
          "tenorMonths": 6,
          "monthlyInterestRate": 0.05
        },
        "calculatedAt": "2026-01-29T10:00:05.000Z"
      },
      "processedAt": "2026-01-29T10:00:05.000Z"
    }
    

    Field Definitions

    FieldDescription
    avgMonthlyGmvAverage monthly GMV across the 6 months
    avgMonthlyOrdersAverage monthly order count across the 6 months
    aovAverage order value (avgMonthlyGmv / avgMonthlyOrders)
    gmvTrendPercentPercentage change from month 1 (oldest) to month 6 (newest). Positive = growth, negative = decline.
    gmvVolatilityCovCoefficient of variation (sample std dev / mean). Lower values indicate more stable revenue.
    maxEligibleLoanMaximum loan amount the merchant qualifies for, based on repayment capacity and policy parameters
    policyParameters.repaymentRateFraction of monthly GMV allocated to repayment (default: 0.25 = 25%)
    policyParameters.tenorMonthsLoan tenor in months (default: 6)
    policyParameters.monthlyInterestRateMonthly interest rate (default: 0.05 = 5%)

    Bulk Submission (Async)

    For submitting data for multiple merchants at once, use the async bulk endpoint.

    Endpoint: POST /api/marketplace/data/merchants/bulk

    Required scope: data.share.sales

    This endpoint accepts an array of merchant data payloads and processes them asynchronously. It returns HTTP 202 Accepted with a batchId you can use to poll for status.

    Request

    curl -X POST https://api.opensylo.com/api/marketplace/data/merchants/bulk \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "merchants": [
          { "merchantIdentity": { "..." : "..." }, "salesPerformance": { "..." : "..." }, "..." : "..." },
          { "merchantIdentity": { "..." : "..." }, "salesPerformance": { "..." : "..." }, "..." : "..." }
        ],
        "marketplaceId": "your_marketplace_uuid",
        "batchTimestamp": "2026-01-29T10:00:00.000Z",
        "batchId": "batch_20260129_001"
      }'
    

    Response (202 Accepted)

    {
      "success": true,
      "message": "Batch accepted for processing",
      "batchId": "batch_20260129_001",
      "jobId": "job_uuid",
      "totalMerchants": 50,
      "status": "pending",
      "statusUrl": "/api/marketplace/data/batch/batch_20260129_001/status",
      "submittedAt": "2026-01-29T10:00:01.000Z"
    }
    

    Polling for Status

    Endpoint: GET /api/marketplace/data/batch/{batchId}/status

    async function pollBatchStatus(
      batchId: string,
      accessToken: string
    ): Promise<BatchStatusResponse> {
      const maxAttempts = 30;
      const pollIntervalMs = 5000; // 5 seconds
    
      for (let attempt = 0; attempt < maxAttempts; attempt++) {
        const response = await fetch(
          `https://api.opensylo.com/api/marketplace/data/batch/${batchId}/status`,
          { headers: { Authorization: `Bearer ${accessToken}` } }
        );
    
        const status = await response.json();
    
        if (status.status === "completed" || status.status === "failed") {
          return status;
        }
    
        // Still processing -- wait before next poll
        await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
      }
    
      throw new Error(`Batch ${batchId} did not complete within timeout`);
    }
    
    // Usage
    const result = await pollBatchStatus("batch_20260129_001", accessToken);
    console.log(`Processed: ${result.successCount}, Failed: ${result.failedCount}`);
    

    Batch Status Response

    {
      "batchId": "batch_20260129_001",
      "status": "completed",
      "totalMerchants": 50,
      "submittedAt": "2026-01-29T10:00:01.000Z",
      "completedAt": "2026-01-29T10:02:30.000Z",
      "successCount": 48,
      "failedCount": 2,
      "results": [
        {
          "merchantId": "merch_049",
          "status": "failed",
          "error": "salesPerformance.gmv30Days must not be less than 0"
        }
      ]
    }
    

    Maximum batch size: 1,000 merchants per request. For larger datasets, split into multiple batches.


    Credit Score Interpretation

    The credit score is a composite score from 0 to 100. Each component has a maximum contribution:

    ComponentMax ScoreWhat It Measures
    salesCount60Sales growth rate and order volume
    revenueConsistency10Monthly variance, zero-sales months, sales streaks
    fulfillmentOps10Fulfillment rate, cancellation/refund/dispute rates
    stabilityTenure10Account age, verification status, industry
    riskBehavior10Suspensions, fraud flags, GMV spike rate, linked accounts
    Perfect-record bonus+10Added to the TOTAL when conduct is clean (see below)

    The five component caps sum to exactly 100. The perfect-record bonus is extra
    headroom applied to the total before the 0-100 clamp, so a clean-record merchant
    can reach 100 without maxing every component. There is no scored
    payoutCashFlow component — that slot is reserved but deliberately unscored.

    Risk Tiers

    TierScore RangeRisk LevelLending Implication
    A80 -- 100LowHighest cap, best pricing
    B65 -- 79LowStandard cap
    C50 -- 64MediumReduced cap, short tenor
    D35 -- 49HighPilot / small ticket only
    E0 -- 34Very HighNot eligible

    Lending Cap

    The maxLendingCap is derived from the merchant's average monthly sales multiplied by the tier's eligible percentage:

    TierEligible %Example (NGN 1M avg monthly)
    A40%NGN 400,000
    B30%NGN 300,000
    C20%NGN 200,000
    D10%NGN 100,000
    E0%NGN 0

    When monthlySalesHistory is provided and loan eligibility is calculated, the maxLendingCap is overwritten with the maxEligibleLoan value from the eligibility calculation (which factors in repayment capacity, tenor, and interest rates).


    How Events Affect Scoring & Eligibility

    OpenSylo supports two data paths into the credit scoring engine. Both produce the same credit score and loan eligibility outputs.

    Path 1: Direct Data Ingestion (Pre-Aggregated)

    The marketplace submits a complete, pre-aggregated data snapshot via POST /api/marketplace/data/merchant. The scoring engine processes it synchronously and returns the credit score in the response. This is ideal when the marketplace already computes aggregate metrics internally.

    Path 2: Event-Based Aggregation (Automatic)

    The marketplace sends individual events as they happen via the sales events, repayment events, and account flags endpoints. OpenSylo automatically aggregates these events and feeds the result into the same scoring engine. This happens asynchronously:

    1. Each event is stored immediately
    2. A 5-minute debounced aggregation job is queued (multiple events within 5 minutes trigger only one recalculation)
    3. The aggregation service queries all events from the last 180 days, computes metrics, and runs credit scoring
    4. A daily sweep at 2 AM catches any merchants with recent events but stale scores

    Minimum requirements for event-based scoring:

    • At least 10 SALE events
    • Events spanning at least 30 days
    • For loan eligibility: at least 3 calendar months with non-zero sales

    Sales Events

    Submit individual sales transactions that feed into credit scoring.

    Endpoint: POST /api/marketplace/sales-events

    Required scope: sales.write

    Event Types and Scoring Impact

    Each event type affects different components of the credit score:

    SALE — Completed sale transaction

    The primary driver of credit scoring. SALE events affect 4 of 6 score components:

    Score ComponentMax PointsHow SALE Events Are Used
    Sales Count60gross_amount is summed over 30/90/180-day windows to compute GMV. Order volume and growth rate are derived.
    Revenue Consistency10Monthly GMV variance (coefficient of variation), zero-sales months, and longest consecutive sales streak are computed from the monthly grouping of SALE events.
    Loan Eligibility—Monthly GMV and order counts from SALE events form the 6-month history used to calculate maxEligibleLoan.

    Sales Count scoring thresholds:

    MetricPointsThresholds
    Sales Growth Rate0–20>20%: 20, >10%: 15, >5%: 10, >0%: 5, >-5%: 1
    Order Volume0–40>1000: 40, >500: 30, >100: 20, >50: 10, >5: 5

    Order volume prefers avgMonthlyOrders (trailing-6-month mean) and falls back to
    orderCount only when that is absent or zero. avgMonthlySales and
    avgOrderValue are not scored — they are still ingested and used for the
    lending cap, but they contribute no points.

    Revenue Consistency scoring thresholds:

    MetricPointsThresholds
    Monthly Sales Variance0–5<10%: 5, <20%: 4, <30%: 2, <50%: 1
    Longest Sales Streak0–5≥12mo: 5, ≥6mo: 4, ≥3mo: 3, ≥1mo: 1
    Zero-Sales Months-0 to -3Penalty of 1 per zero month (max -3)

    REFUND — Refund issued to buyer

    Score ComponentMax PointsHow REFUND Events Are Used
    Fulfillment Ops10refundRate = (REFUND count / SALE count) × 100. Combined with disputeRate for a joint threshold check. refundAmount = sum of gross_amount from all REFUND events.
    Risk Flags—If refundRate > 10%, the flag HIGH_REFUND_RATE is raised.

    CANCELLATION — Order cancelled before fulfillment

    Score ComponentMax PointsHow CANCELLATION Events Are Used
    Fulfillment Ops10cancellationRate = (CANCELLATION count / SALE count) × 100. Also used to compute fulfillmentRate = 100 - cancellationRate - refundRate.

    Fulfillment Ops scoring thresholds:

    MetricPointsThresholds
    Fulfillment Rate0–6≥98%: 6, ≥95%: 5, ≥90%: 4, ≥85%: 3, ≥80%: 2
    Cancellation Rate0–2<2%: 2, <5%: 1, <10%: 1, <15%: 1
    Refund + Dispute Rate0–2refund<1% & dispute<1%: 2, refund<3% & dispute<2%: 1, refund<5% & dispute<5%: 0

    lateDeliveryRate is ingested but not scored.

    CHARGEBACK — Payment dispute resolved in buyer's favor

    Score ComponentMax PointsHow CHARGEBACK Events Are Used
    Fulfillment Ops10disputeRate = (CHARGEBACK count / SALE count) × 100. Combined with refundRate for joint scoring.
    Risk Flags—If disputeRate > 5%, the flag HIGH_DISPUTE_RATE is raised.

    ADJUSTMENT — Manual adjustment

    Stored for audit purposes. Does not currently affect credit scoring.

    Example

    curl -X POST https://api.opensylo.com/api/marketplace/sales-events \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "event_id": "evt_sale_20260407_001",
        "platform_merchant_id": "PLT_MERCH_001",
        "event_type": "SALE",
        "event_date": "2026-04-07T14:30:00.000Z",
        "order_id": "ORD-2026-12345",
        "customer_id": "CUST_001",
        "gross_amount": 45000,
        "net_amount": 42750,
        "commission_amount": 2250,
        "currency": "NGN",
        "payment_method": "card",
        "items": [{ "name": "Ankara Dress", "quantity": 1, "price": 45000 }],
        "idempotency_key": "sale_evt_20260407_12345"
      }'
    

    The idempotency_key prevents duplicate processing. If you send the same key twice, the second request returns the original result without creating a duplicate event.


    Repayment Events

    Report loan repayment collections from merchant sales.

    Endpoint: POST /api/marketplace/repayment-events

    Required scope: repayments.write

    Event Types

    TypeDescription
    COLLECTIONA repayment amount collected from a sale
    DEDUCTIONA repayment deducted from merchant settlement
    REFUNDA repayment refunded back to the merchant
    REVERSALA previously collected repayment reversed

    Scoring Impact

    Repayment events do not directly contribute points to the credit score. However, each repayment event triggers a credit score recalculation using the latest sales event data. This means:

    • A merchant's score stays fresh as long as repayment events are flowing
    • If new sales events have accumulated since the last score calculation, the repayment event will cause those to be picked up
    • The repayment data itself is stored and used by the separate loan repayment processing pipeline (settlement, wallet debits, audit trails)

    Example

    curl -X POST https://api.opensylo.com/api/marketplace/repayment-events \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "event_id": "evt_repay_20260407_001",
        "platform_merchant_id": "PLT_MERCH_001",
        "event_type": "COLLECTION",
        "collection_date": "2026-04-07",
        "loan_id": "loan_f1e2d3c4-b5a6-7890-1234-567890abcdef",
        "scheduled_amount": 5000,
        "collected_amount": 5000,
        "outstanding_amount": 1375000,
        "currency": "NGN",
        "collection_reference": "COL-20260407-001",
        "payment_reference": "FLW-COL-20260407-001",
        "collection_method": "sales_deduction",
        "idempotency_key": "idem_repay_20260407_001"
      }'
    

    Account Flags

    Report account-level risk events that directly affect a merchant's credit score.

    Endpoint: POST /api/marketplace/account-flags

    Required scope: account.flags.write

    Flag Types and Scoring Impact

    Account flags feed into the Risk Behavior score component (max 10 points). The risk behavior score starts at 10 and is reduced by penalties:

    Flag TypeScoring Impact
    ACCOUNT_SUSPENDED-2 points per suspension (max -5 total). Each active suspension flag directly reduces the score.
    FRAUD_SUSPECTED-3 points. Any active fraud flag triggers this penalty.
    POLICY_VIOLATIONStored as context. Does not directly reduce points (the calculator does not consume policyViolationsSeverity), but signals risk to human reviewers.
    HIGH_CHARGEBACK_RATEStored as context. The chargeback impact comes from CHARGEBACK sales events instead.
    HIGH_REFUND_RATEStored as context. The refund impact comes from REFUND sales events instead.
    ACCOUNT_CLOSEDStored as context.
    PAYMENT_DELAYStored as context.
    UNDERPERFORMANCEStored as context.
    VERIFICATION_PENDINGStored as context.
    VERIFICATION_FAILEDStored as context.
    GOOD_STANDINGStored as context (positive signal).
    TOP_PERFORMERStored as context (positive signal).

    Risk Behavior score calculation:

    Start: 10 points
    - accountSuspensions:  -2 per suspension (max -5)
    - hasFraudFlags:       -3
    - GMV spikes:          -1 to -3, by SHARE of months that spiked
                           (>=30% -> -3, >=15% -> -2, any -> -1; see below)
    - hasLinkedAccounts:    -1  (detected from data ingestion, not derivable from events)
    Minimum: 0 points
    

    How GMV spikes are scored. A month is a spike when it is >= 3x the prior
    month and fails to hold (the next month falls below 50% of it). A jump the
    next month sustains is a step change - growth, not risk - and is not counted.
    The deduction scales with gmvSpikeMonthsPercent, the share of observed months
    that spiked, so one odd month in a long history is noise while spikes in a
    third of the months are a pattern.

    gmvSpikeMonthsPercent is optional. Omit it and the scorer falls back to a
    flat -2 on hasSuddenGmvSpikes, exactly as before - no integration change is
    required. Send it to get the graduated treatment.

    GMV spikes do not affect the perfect-record bonus. Only suspensions, fraud
    flags and linked accounts gate that.

    Each account flag event also triggers a credit score recalculation, so the impact is reflected immediately (after the 5-minute debounce window).

    Flag Severities

    SeverityDescription
    INFOInformational only
    WARNINGMay affect future credit decisions
    HIGHImmediate review of active loans triggered
    CRITICALLoan disbursements frozen, escalation to risk team

    Example

    curl -X POST https://api.opensylo.com/api/marketplace/account-flags \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "platform_merchant_id": "PLT_MERCH_001",
        "flag_type": "ACCOUNT_SUSPENDED",
        "flag_severity": "HIGH",
        "description": "Merchant refund rate exceeded 10% threshold for 3 consecutive months.",
        "flagged_at": "2026-04-07T12:00:00.000Z",
        "evidence": {
          "refund_rate_3m": 12.5,
          "refund_count": 45,
          "total_orders": 360
        },
        "idempotency_key": "idem_flag_20260407_001"
      }'
    

    Stability & Tenure Score

    This component (max 10 points) is derived from the merchant's profile, not from events:

    MetricPointsThresholds
    Account Age0–5≥24mo: 5, ≥12mo: 4, ≥6mo: 3, ≥3mo: 2, ≥1mo: 1
    Verification Status0–3KYC Approved: 3, otherwise: 0
    Business Category1–2Low-risk categories (electronics, books, clothing, home & garden): 2, others: 1

    Account age is computed from the marketplace connection date (when the merchant connected to OpenSylo via OAuth).


    Payout Cash Flow Score

    This component (max 15 points) behaves differently depending on the data path:

    Direct data ingestion: The marketplace provides payoutCashFlow with actual payout metrics.

    Event-based aggregation: Defaults are used since payout data cannot be derived from sales events:

    • payoutFrequency: MONTHLY (assumed)
    • avgPayoutValue: Average monthly net sales (gross - refunds)
    • failedPayouts: 0 (assumed)
    MetricPointsThresholds
    Failed Payouts0–90: 9, <2: 7, <5: 5, <10: 3
    Avg Payout Value0–6>₦100K: 6, >₦50K: 5, >₦25K: 4, >₦10K: 3, >₦5K: 2

    Loan Eligibility Calculation

    Loan eligibility requires 6 months of monthly sales history. This is provided either directly via monthlySalesHistory in the data ingestion payload, or automatically derived from SALE events grouped by calendar month.

    When derived from events, eligibility is only computed if at least 3 of the 6 months have non-zero GMV.

    Calculation Formula

    avgMonthlyGmv         = sum(6 months GMV) / 6
    monthlyRepaymentCap   = avgMonthlyGmv × repaymentRate         (default 25%)
    totalRepaymentCap     = monthlyRepaymentCap × tenorMonths      (default 6)
    interestMultiplier    = 1 + (monthlyInterestRate × tenorMonths) (default 1.30)
    maxEligibleLoan       = totalRepaymentCap / interestMultiplier
    

    Worked Example

    Given 6 months of GMV: ₦950K, ₦1.02M, ₦1.1M, ₦1.05M, ₦1.25M, ₦1.2M

    avgMonthlyGmv         = ₦6,570,000 / 6     = ₦1,095,000
    monthlyRepaymentCap   = ₦1,095,000 × 0.25  = ₦273,750
    totalRepaymentCap     = ₦273,750 × 6       = ₦1,642,500
    interestMultiplier    = 1 + (0.05 × 6)     = 1.30
    maxEligibleLoan       = ₦1,642,500 / 1.30  = ₦1,263,461.54
    

    Additional Eligibility Metrics

    MetricDescription
    gmvTrendPercent(newest month GMV - oldest month GMV) / oldest month GMV × 100. Positive = growth.
    gmvVolatilityCovCoefficient of variation (sample std dev / mean). Lower = more stable.
    aovAverage order value = avgMonthlyGmv / avgMonthlyOrders

    Policy Parameters

    Loan eligibility uses policy parameters that can be configured per-marketplace or system-wide:

    ParameterDefaultDescription
    repaymentRate0.25 (25%)Fraction of monthly GMV allocated to loan repayment
    tenorMonths6Loan duration in months
    monthlyInterestRate0.05 (5%)Monthly interest rate applied to the loan

    When monthlySalesHistory is provided and eligibility is calculated, the maxLendingCap on the credit score is overwritten with maxEligibleLoan (which is typically more accurate since it factors in repayment capacity, tenor, and interest).


    Complete Scoring Summary

    Score ComponentMaxData SourcesEvent-Based Inputs
    Sales Count60salesPerformanceSALE events (gross_amount, count)
    Revenue Consistency10revenueConsistencySALE events (monthly grouping)
    Fulfillment Ops10fulfillmentMetricsREFUND, CANCELLATION, CHARGEBACK events
    Stability & Tenure10merchantIdentityMerchant profile + connection date
    Risk Behavior10behavioralRiskAccount flag events + GMV spike shape
    Component total100
    Perfect-record bonus+10behavioralRiskApplied to the total, pre-clamp

    Risk Tier → Lending Decision

    TierScoreEligible % of Avg Monthly SalesLending Decision
    A80–10080%Highest cap, best pricing
    B65–7950%Standard cap
    C50–6420%Reduced cap, short tenor
    D35–4910%Pilot / small ticket only
    E0–340%Not eligible
    Modified at 2026-08-05 07:02:04
    Next
    Errors
    Built with