DiDi Delivery: Deliver & Earn icon

DiDi Delivery courier trip and payout data API

DiDi Global · Food Delivery 3.0 ★

DiDi Delivery: Deliver & Earn is DiDi Global's courier app for DiDi Food, used by independent riders who pick up restaurant orders and drop them off on foot, by bicycle or by scooter. Couriers create an account with a mobile number, upload identification, and complete an in-person check at a DiDi Courier Center before they can go online and accept jobs. Once signed in they work from a home that lists available deliveries, open a live job for pickup and drop-off, review trip income after each hand-off, and cash out earnings through DiDi Pay and the in-app wallet. The Play listing is live for Mexico and Chile among other Latin American DiDi Food cities; the app holds a 3.0 rating from 92,942 reviews. Riders in those markets often run it next to Soy Rappi and Uber Eats for the same food-delivery shifts.

Pickup takeInfo lists shopName, shopAddress and cashAdvances; drop-off SendInfo carries customerName, customerAddress and cashReceivable, with isPhoneProtect on the customer number. Rider profile wire names include ID, realname, phone, workType and cityID. Wallet accountSection hangs balance next to kycStatus, fullKycStatus and bound cardNum; the balance-detail snapshot adds totalBalance, totalEarning and cashback, while statement rows keep transId, transDate and amountText.

A cash-out quote returns channelName, inputAmount and commissionAmount before canWithdraw and availableBalance update. The withdraw profile carries legal name and idNo; progress rows expose withdrawId, bankCard and predictionTime. Fleet boards overlay the live pickup; tax tools reconcile statement lines; payout desks settle on bindCard bank rows. openData Studio turns those fields into callable open data.

Screenshots

  • DiDi Delivery: Deliver & Earn screenshot 1
  • DiDi Delivery: Deliver & Earn screenshot 2
  • DiDi Delivery: Deliver & Earn screenshot 3
  • DiDi Delivery: Deliver & Earn screenshot 4
  • DiDi Delivery: Deliver & Earn screenshot 5
  • DiDi Delivery: Deliver & Earn screenshot 6

API surface

The endpoints and request/response examples below are reconstructed from the app's interface — illustrative, not a live capture.

  • Send login verification code

    POST /v1/didi-food/session/otp opendata

    Sends an SMS or email one-time code for courier sign-in; returns errno plus prompt and voiceSupport.

    Auth: None (starts login). Body carries cell or email plus code_type; BaseParam adds country_id, country_calling_code, canonical_country_code, appid, role, lang.

    • cell
    • code_type
    • os_type
    • country_id
    • country_calling_code
    • canonical_country_code
    • api_version
    • app_version
    • lang
    • errno
    • error
    • traceId
    • prompt
    • voiceSupport
    POST /v1/didi-food/session/otp HTTP/1.1
    Content-Type: application/json
    
    {
      "cell": "5215512345678",
      "code_type": 1,
      "os_type": "android",
      "country_id": 52,
      "country_calling_code": "+52",
      "canonical_country_code": "MX",
      "api_version": "1.0.2",
      "app_version": "2.2.78",
      "lang": "es-MX"
    }
    {
      "errno": 0,
      "error": "",
      "traceId": "tr_9c81e2b0",
      "code_type": 1,
      "prompt": "We sent a code to **5678",
      "voiceSupport": true
    }
  • Sign in by verification code

    POST /v1/didi-food/session/verify osint

    Exchanges the one-time code for a passport ticket, uid, cell and countryId used by later wallet, payout and rider RPC calls.

    Auth: None (issues ticket). Body carries cell, code, codeType and loginType; extra_info may set code_type.

    • cell
    • code
    • codeType
    • loginType
    • extra_info
    • ticket
    • uid
    • email
    • countryId
    • callingCode
    • role
    • userType
    • verifySessionId
    • errno
    • traceId
    POST /v1/didi-food/session/verify HTTP/1.1
    Content-Type: application/json
    
    {
      "cell": "5215512345678",
      "code": "482193",
      "codeType": 1,
      "loginType": 1,
      "country_id": 52,
      "country_calling_code": "+52",
      "canonical_country_code": "MX",
      "api_version": "1.0.2",
      "app_version": "2.2.78",
      "extra_info": {
        "code_type": 1
      }
    }
    {
      "errno": 0,
      "error": "",
      "traceId": "tr_9c81e2b0",
      "ticket": "tk_8f21c0a4",
      "uid": 1843921001,
      "cell": "5215512345678",
      "email": "",
      "countryId": 52,
      "callingCode": "+52",
      "role": 2,
      "userType": 1,
      "verifySessionId": "vs_44102"
    }
  • Refresh passport ticket

    POST /v1/didi-food/session/renew opendata

    Renews the passport ticket so wallet, DiDi Pay and rider RPC sessions stay signed in without another OTP.

    Auth: Existing ticket in the JSON body; optional returncell flag.

    • ticket
    • returncell
    • errno
    • uid
    • cell
    • countryId
    • callingCode
    POST /v1/didi-food/session/renew HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "returncell": 1,
      "canonical_country_code": "MX",
      "api_version": "1.0.2",
      "app_version": "2.2.78"
    }
    {
      "errno": 0,
      "error": "",
      "ticket": "tk_c91e2b77",
      "uid": 1843921001,
      "cell": "5215512345678",
      "countryId": 52,
      "callingCode": "+52"
    }
  • Get rider profile

    POST /v1/didi-food/courier osint

    Loads the signed-in courier: ID, realname, phone, workType, cityID / countyID and countryCode used on the rider home.

    Auth: Login token from the verification-code sign-in as form field token.

    • token
    • ID
    • phone
    • realname
    • nickname
    • headImg
    • countyGroupID
    • countyID
    • cityID
    • areaID
    • status
    • workType
    • channel
    • level
    • vehicleType
    • countryCode
    • country
    • vendorCode
    • isShopper
    • ttsSwitch
    POST /v1/didi-food/courier HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    token=tk_8f21c0a4
    {
      "ID": "1843921001",
      "phone": "5215512345678",
      "realname": "Maria Lopez",
      "nickname": "Maria",
      "headImg": "",
      "countyGroupID": "mx-cdmx",
      "countyID": "09000",
      "cityID": "51234500",
      "areaID": "51234501",
      "status": 1,
      "workType": 1,
      "channel": 1,
      "level": "2",
      "vehicleType": "bike",
      "countryCode": "MX",
      "country": "Mexico",
      "vendorCode": "didi_food",
      "isShopper": false,
      "ttsSwitch": true
    }
  • Get trip delivery detail

    POST /v1/didi-food/jobs/{deliveryId} opendata

    Opens a live DiDi Food job: pickup takeInfo (shopName, shopAddress, cashAdvances) and drop-off SendInfo (customerName, cashReceivable, isPhoneProtect).

    Auth: Login token from the verification-code sign-in as form field token.

    • token
    • deliveryId
    • takeInfo
    • orderIndex
    • shopName
    • shopAddress
    • shopPhone
    • cashAdvances
    • isShopPhoneProtect
    • customerName
    • customerPhone
    • customerPoiName
    • customerAddress
    • customerPoiAddress
    • cashReceivable
    • hideCustomerAddress
    • isPhoneProtect
    • name
    • currency
    • fee
    • desc
    POST /v1/didi-food/jobs/{deliveryId} HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    token=tk_8f21c0a4&deliveryId=dl_7f21c0a4
    {
      "takeInfo": {
        "orderIndex": "1",
        "shopName": "Tacos El Guero",
        "shopAddress": "Av. Insurgentes 120",
        "shopPhone": "5551230000",
        "cashAdvances": "0.00",
        "isShopPhoneProtect": 1
      },
      "sendInfo": {
        "customerName": "Ana",
        "customerPhone": "5559870000",
        "customerPoiName": "Depto 4B",
        "customerAddress": "Calle Reforma 88",
        "customerPoiAddress": "Calle Reforma 88",
        "cashReceivable": "128.50",
        "hideCustomerAddress": 0,
        "isPhoneProtect": 1
      },
      "fee": {
        "name": "Delivery fee",
        "currency": "MXN",
        "fee": 28.5,
        "desc": "Base + distance"
      }
    }
  • Get hot-zone order forecast

    POST /v1/didi-food/demand/forecast opendata

    Returns the order-forecast widget: refreshTime plus columnList rows of num and mark for the hot-zone chart.

    Auth: Login token from the verification-code sign-in as form field token.

    • token
    • defaultInfo
    • refreshTime
    • columnList
    • num
    • mark
    POST /v1/didi-food/demand/forecast HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    token=tk_8f21c0a4
    {
      "defaultInfo": {},
      "refreshTime": 60,
      "columnList": [
        {
          "num": 12.0,
          "mark": 1
        },
        {
          "num": 18.0,
          "mark": 0
        }
      ]
    }
  • Query courier wallet home balances

    POST /v1/didi-food/wallet openbanking

    Loads the wallet home snapshot: accountBalance.balance, kycStatus / fullKycStatus, and bound bankCard.cardNum.

    Auth: Passport ticket from the verification-code sign-in call, sent as ticket in the JSON body.

    • ticket
    • errno
    • errmsg
    • data
    • channelId
    • needFuidLogin
    • accountSection
    • accountBalance
    • balance
    • title
    • linkUrl
    • authenticationStatus
    • kycStatus
    • fullKycStatus
    • supportFullKyc
    • bankCard
    • cardNum
    • bhasInterest
    • paySection
    • financialSection
    POST /v1/didi-food/wallet HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "canonical_country_code": "MX",
      "lang": "es-MX"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "channelId": "MX_WALLET",
        "needFuidLogin": false,
        "accountSection": {
          "accountSectionName": "DiDi Pay",
          "accountBalance": {
            "balance": "1850.40",
            "title": "Available",
            "linkUrl": ""
          },
          "authenticationStatus": "VERIFIED",
          "kycStatus": "PASS",
          "fullKycStatus": "PASS",
          "supportFullKyc": true,
          "bankCard": {
            "cardNum": "****1234",
            "title": "BBVA",
            "linkUrl": "",
            "redDot": "0"
          },
          "bhasInterest": false
        },
        "paySection": {},
        "financialSection": {}
      }
    }
  • Read account balance info

    POST /v1/didi-food/wallet/balance openbanking

    Loads the balance-detail snapshot: totalBalance / totalEarning, cashback and interests, plus withoutHistory for the empty state.

    Auth: Passport ticket from the verification-code sign-in call, sent as ticket in the JSON body.

    • ticket
    • errno
    • errmsg
    • data
    • totalBalance
    • totalBalanceLabel
    • totalBalanceTitle
    • totalEarning
    • totalEarningLabel
    • cashback
    • cashbackLabel
    • interests
    • interestLabel
    • hasHit
    • withoutHistory
    • judBlockedBalance
    • marketingResourceEntrance
    POST /v1/didi-food/wallet/balance HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "totalBalance": 185040,
        "totalBalanceLabel": "$1,850.40",
        "totalBalanceTitle": "Available",
        "totalEarning": 12850,
        "totalEarningLabel": "$128.50",
        "cashback": 450,
        "cashbackLabel": "$4.50",
        "interests": 12,
        "interestLabel": "$0.12",
        "hasHit": true,
        "withoutHistory": false,
        "judBlockedBalance": {
          "amount": 0
        },
        "marketingResourceEntrance": {}
      }
    }
  • List account statements

    POST /v1/didi-food/wallet/ledger openbanking

    Returns wallet history rows with transId, businessType, transDate and amountText for the statements screen.

    Auth: Passport ticket from the verification-code sign-in call, sent as ticket in the JSON body.

    • ticket
    • nextIndex
    • linkUrl
    • errno
    • statement
    • title
    • transId
    • transDate
    • transDateTitle
    • time
    • businessType
    • detailLink
    • amountText
    POST /v1/didi-food/wallet/ledger HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "nextIndex": 0
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "nextIndex": 20,
        "linkUrl": "",
        "statement": [
          {
            "title": "DiDi Food delivery",
            "transId": "txn_7f21c0a4",
            "transDate": "2026-10-03",
            "transDateTitle": "3 Oct",
            "time": 1759507200000,
            "businessType": "DELIVERY",
            "detailLink": "",
            "amountText": {
              "text": "$128.50"
            }
          }
        ]
      }
    }
  • Read account KYC status

    POST /v1/didi-food/wallet/kyc osint

    Reads fullKycStatus and status that gate wallet cash-out and the Courier Center identity check.

    Auth: Passport ticket from the verification-code sign-in call, sent as ticket in the JSON body.

    • ticket
    • errno
    • errmsg
    • data
    • fullKycStatus
    • status
    POST /v1/didi-food/wallet/kyc HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "fullKycStatus": 1,
        "status": 1
      }
    }
  • Calculate manual withdraw

    POST /v1/didi-food/payouts/quote openfinance

    Quotes a cash-out: channelName, inputAmount, oriAmount and commissionAmount before the courier confirms.

    Auth: Passport ticket from the verification-code sign-in call, sent as ticket in the JSON body. Client also sends utc_offset, osChannel=Android, app_version.

    • ticket
    • inputAmount
    • utc_offset
    • osChannel
    • app_version
    • errno
    • data
    • channelName
    • oriAmount
    • commissionAmount
    POST /v1/didi-food/payouts/quote HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "inputAmount": [
        "500.00"
      ],
      "utc_offset": -360,
      "osChannel": "Android",
      "app_version": "2.2.78"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "channelName": "SPEI",
        "inputAmount": [
          "500.00"
        ],
        "oriAmount": [
          "500.00"
        ],
        "commissionAmount": [
          "8.50"
        ]
      }
    }
  • Create manual withdraw

    POST /v1/didi-food/payouts openfinance

    Submits a cash-out and returns DriverWithdrawInfo: availableBalance, canWithdraw, hasBindAccount and bindCard rows.

    Auth: Passport ticket from the verification-code sign-in call, sent as ticket in the JSON body.

    • ticket
    • inputAmount
    • channelName
    • errno
    • data
    • driverInfo
    • availableBalance
    • availableBalanceSplit
    • realBalance
    • canWithdraw
    • hasBindAccount
    • showTransferIn
    • bindCard
    • bankCode
    • bankName
    • cardNum
    • cardStatus
    • commissionFee
    • withdrawMin
    • withdrawMax
    POST /v1/didi-food/payouts HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "inputAmount": [
        "500.00"
      ],
      "channelName": "SPEI",
      "osChannel": "Android",
      "app_version": "2.2.78"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "driverInfo": {
          "availableBalance": 135040,
          "availableBalanceSplit": [
            "1,350.40"
          ],
          "realBalance": 135040,
          "realBalanceSplit": [
            "1,350.40"
          ],
          "canWithdraw": true,
          "hasBindAccount": true,
          "showTransferIn": false,
          "bindCard": [
            {
              "bankCode": "012",
              "bankName": "BBVA",
              "cardNum": "****1234",
              "cardStatus": 1,
              "commissionFee": 850,
              "commissionFeeDisplay": 8.5,
              "withdrawMin": 10000,
              "withdrawMax": 5000000,
              "isDefault": true
            }
          ]
        }
      }
    }
  • Create DiDi Pay order

    POST /v1/didi-food/wallet/checkout openfinance

    Opens a DiDi Pay cashier order and returns orderId, outTradeId and cashierType for the payment sheet.

    Auth: Passport ticket from the verification-code sign-in call, sent as ticket in the JSON body.

    • ticket
    • errno
    • data
    • orderMetaData
    • orderId
    • outTradeId
    • cashierType
    • jumpToH5
    • thirdPartyParams
    • noticeInfo
    • paymentPasswordInfo
    POST /v1/didi-food/wallet/checkout HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "orderMetaData": {
          "orderId": "ord_8f21c0a4",
          "outTradeId": "ot_44102",
          "cashierType": 1,
          "jumpToH5": false,
          "thirdPartyParams": "",
          "noticeInfo": {},
          "paymentPasswordInfo": {}
        }
      }
    }
  • Query driver withdraw profile

    POST /v1/didi-food/payouts/profile osint

    Loads the courier's withdraw profile: legal name, idNo, fastTrackStatus and bindChannelCodeList used by the bank-account screen.

    Auth: Passport ticket from the verification-code sign-in call, sent in the JSON body with utc_offset, osChannel=Android, app_version.

    • ticket
    • utc_offset
    • osChannel
    • app_version
    • errno
    • data
    • name
    • idNo
    • status
    • isAbnormal
    • fastTrackStatus
    • fastTrackStatusDesc
    • freeWithdraw
    • bindCardRule
    • bindChannelCodeList
    • isDefaultChannel
    • lastWithdrawRetMsg
    • withdrawGray
    POST /v1/didi-food/payouts/profile HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "utc_offset": -360,
      "osChannel": "Android",
      "app_version": "2.2.78",
      "canonical_country_code": "MX"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "name": "Maria Lopez",
        "idNo": "LOPM850101XXXX",
        "status": 1,
        "isAbnormal": false,
        "fastTrackStatus": "PASS",
        "fastTrackStatusDesc": "Verified",
        "freeWithdraw": 2,
        "bindCardRule": "SPEI",
        "bindChannelCodeList": [
          "SPEI",
          "CLABE"
        ],
        "isDefaultChannel": true,
        "lastWithdrawRetMsg": "",
        "withdrawGray": 0
      }
    }
  • Query withdraw card channel status

    POST /v1/didi-food/payouts/channels openbanking

    Lists bound payout channels with channelId, accountTitle, status and isDefaultChannel for the bank-account manager.

    Auth: Passport ticket from the verification-code sign-in call, sent in the JSON body. Also sets caller=fintech_sdk.

    • ticket
    • caller
    • errno
    • data
    • channelId
    • accountTitle
    • status
    • isAbnormal
    • isDefaultChannel
    • lastWithdrawRetMsg
    • withdrawGray
    • icon
    POST /v1/didi-food/payouts/channels HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "caller": "fintech_sdk",
      "osChannel": "Android",
      "app_version": "2.2.78"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": [
        {
          "channelId": "SPEI",
          "accountTitle": "BBVA ****1234",
          "status": "ACTIVE",
          "isAbnormal": false,
          "isDefaultChannel": 1,
          "lastWithdrawRetMsg": "",
          "withdrawGray": 0,
          "icon": ""
        }
      ]
    }
  • Query withdrawal progress

    POST /v1/didi-food/payouts/{withdrawId} openfinance

    Tracks a cash-out by withdrawId: status, amount, commissionFee, bankName / bankCard and createTime / predictionTime timestamps.

    Auth: Passport ticket from the verification-code sign-in call, sent in the JSON body with utc_offset, osChannel and app_version.

    • ticket
    • withdrawId
    • errno
    • data
    • status
    • amount
    • amountSplit
    • originalAmount
    • commissionFee
    • bankName
    • bankCard
    • withdrawMethodName
    • createTime
    • submitTime
    • predictionTime
    • finishTime
    • bounceTime
    • createContent
    • submitContent
    • finishContent
    • bounceContent
    • isDisplayRule
    • accountErrorLink
    POST /v1/didi-food/payouts/{withdrawId} HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "withdrawId": "wd_7f21c0a4",
      "osChannel": "Android",
      "app_version": "2.2.78"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "withdrawId": "wd_7f21c0a4",
        "status": 2,
        "amount": "500.00",
        "amountSplit": [
          "500.00"
        ],
        "originalAmount": "500.00",
        "commissionFee": "8.50",
        "bankName": "BBVA",
        "bankCard": "****1234",
        "withdrawMethodName": "SPEI",
        "createTime": 1759507200000,
        "submitTime": 1759507260000,
        "predictionTime": 1759593600000,
        "finishTime": 0,
        "bounceTime": 0,
        "createContent": "Requested",
        "submitContent": "Sent to bank",
        "finishContent": "",
        "bounceContent": "",
        "isDisplayRule": true,
        "accountErrorLink": ""
      }
    }
  • Open driver face-verify session

    POST /v1/didi-food/kyc/face-session osint

    Issues a face-verify session id (session, bizCode) before the courier can bind a payout account.

    Auth: Passport ticket from the verification-code sign-in call, sent in the JSON body. Used when open-account risk requires face verify.

    • ticket
    • errno
    • errmsg
    • data
    • session
    • bizCode
    POST /v1/didi-food/kyc/face-session HTTP/1.1
    Content-Type: application/json
    
    {
      "ticket": "tk_8f21c0a4",
      "osChannel": "Android",
      "app_version": "2.2.78"
    }
    {
      "errno": 0,
      "errmsg": "",
      "data": {
        "session": "fs_9c81e2b0",
        "bizCode": 1001
      }
    }

Data categories

  • deliveries
  • balances
  • transactions
  • payouts
  • kyc
  • identity

Where teams use this data

  • Live trip overlay

    Dispatch tools read takeInfo.shopName / shopAddress and SendInfo.cashReceivable / customerAddress so a fleet board can show the current DiDi Food pickup and drop-off without scraping the courier screen.

  • Courier wallet reconciliation

    Nightly jobs read accountBalance.balance plus statement transId / transDate / amountText so a fleet or tax tool can match DiDi Food payouts against its own ledger.

  • Cash-out ops

    Payout desks consume channelName, commissionAmount and inputAmount from the calculate step, then settle on availableBalance, canWithdraw and bindCard.cardNum / bankName after create.

  • KYC gate for withdrawals

    Compliance overlays watch kycStatus, fullKycStatus and authenticationStatus on the wallet home, plus rider ID / realname / workType, before enabling cash-out.

Frequently asked questions

How do DiDi Delivery couriers authenticate?

The app sends a one-time code to a mobile number or email, then exchanges it for a passport ticket, uid, cell and countryId. Wallet and payout calls send that ticket in the JSON body; rider RPC posts it as form field token. A refresh call renews the ticket without another OTP.

What trip fields does a live delivery expose?

A live job returns takeInfo.shopName, shopAddress, shopPhone and cashAdvances for pickup, plus SendInfo.customerName, customerAddress, cashReceivable and isPhoneProtect for drop-off. Rider home adds ID, realname, phone, workType, cityID and countryCode.

What wallet fields does the courier home expose?

The wallet home returns accountSection.accountBalance.balance, kycStatus, fullKycStatus, authenticationStatus and bankCard.cardNum. The balance-detail snapshot adds totalBalance, totalEarning and cashback. Statement history adds transId, transDate, businessType and amountText.

How does cash-out work?

A calculate step quotes channelName, inputAmount, oriAmount and commissionAmount. Creating the cash-out then returns availableBalance, canWithdraw, hasBindAccount and bindCard rows (bankCode, bankName, cardNum, withdrawMin, withdrawMax).

Topics

  • DiDi Delivery API
  • DiDi Food courier wallet
  • DiDi Pay withdraw
  • DiDi Delivery trips
  • takeInfo shopName
  • cashReceivable
  • courier KYC status

Need this app's data API integrated?

We deliver scoped integrations for any named app — from USD 500 with source-code handoff, or hosted access billed per call. Tell us the data you need.

  • NDA + SOW on every engagement
  • Delivery in 3–7 days
  • Payment only after acceptance
  • Work scoped to authorized use

Get a quote