You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用NetSuite SOAP API遇INVALID LOGIN ATTEMPT错误,Postman可正常运行

问题诊断与修复方案

1. Token Passport 缺少命名空间

NetSuite的SOAP请求中,tokenPassport元素必须指定对应版本的命名空间,否则服务端无法识别身份验证信息,这是触发INVALID LOGIN ATTEMPT的核心原因。需为tokenPassport及相关头部元素添加匹配的命名空间(示例使用2017_1版本,命名空间为urn:messages_2017_1.platform.webservices.netsuite.com)。

2. 签名生成未做URL编码

NetSuite要求签名的Base String中,每个参数都需按照RFC 3986标准做URL编码。原代码直接拼接字符串未编码特殊字符,会导致签名不匹配。

3. 账号变量语法错误

原代码中account = "{account_id" 缺少闭合大括号,会引发字符串解析错误,需修正为account = "YOUR_ACCOUNT_ID"(实际使用时替换为真实账号ID)。

4. 请求URL占位符未替换

URL中的{account_id}和service_id需替换为真实的NetSuite账号ID和对应版本的服务端口(如NetSuitePort_2017_1)。


修正后的完整代码

import requests
import hashlib
import hmac
import time
import random
from urllib.parse import quote_plus

# 替换为真实的NetSuite凭证
account = "YOUR_ACCOUNT_ID"
consumer_key = "YOUR_CONSUMER_KEY"
consumer_secret = "YOUR_CONSUMER_SECRET"
token_id = "YOUR_TOKEN_ID"
token_secret = "YOUR_TOKEN_SECRET"

# 生成签名参数
nonce = str(random.getrandbits(64))
timestamp = str(int(time.time()))

# 对每个签名参数做URL编码
encoded_account = quote_plus(account)
encoded_consumer_key = quote_plus(consumer_key)
encoded_token_id = quote_plus(token_id)
encoded_nonce = quote_plus(nonce)
encoded_timestamp = quote_plus(timestamp)

# 构造签名Base String与密钥
base_string = f"{encoded_account}&{encoded_consumer_key}&{encoded_token_id}&{encoded_nonce}&{encoded_timestamp}"
key = f"{quote_plus(consumer_secret)}&{quote_plus(token_secret)}"

# 生成HMAC-SHA256签名
signature = hmac.new(key.encode(), base_string.encode(), hashlib.sha256).digest().hex()

# 真实请求URL
url = f"https://{account}.suitetalk.api.netsuite.com/services/NetSuitePort_2017_1"
headers = {
    "Content-Type": "text/xml",
    "SOAPAction": "get",
}

# 修正命名空间后的SOAP请求体
body = f"""<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:msg="urn:messages_2017_1.platform.webservices.netsuite.com"
               xmlns:core="urn:core_2017_1.platform.webservices.netsuite.com">
  <soap-env:Header>
    <msg:tokenPassport>
      <msg:account>{account}</msg:account>
      <msg:consumerKey>{consumer_key}</msg:consumerKey>
      <msg:token>{token_id}</msg:token>
      <msg:nonce>{nonce}</msg:nonce>
      <msg:timestamp>{timestamp}</msg:timestamp>
      <msg:signature algorithm="HMAC-SHA256">{signature}</msg:signature>
    </msg:tokenPassport>
    <msg:preferences>
      <msg:runServerSuiteScriptAndTriggerWorkflows>false</msg:runServerSuiteScriptAndTriggerWorkflows>
    </msg:preferences>
    <msg:searchPreferences>
      <msg:pageSize>1000</msg:pageSize>
      <msg:bodyFieldsOnly>false</msg:bodyFieldsOnly>
    </msg:searchPreferences>
  </soap-env:Header>
  <soap-env:Body>
    <msg:get>
      <msg:baseRef internalId="4364" type="job" xsi:type="core:RecordRef" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <core:name/>
      </msg:baseRef>
    </msg:get>
  </soap-env:Body>
</soap-env:Envelope>"""

# 发送请求并打印响应
response = requests.post(url, headers=headers, data=body)
print(response.text)

关于Postman返回单条记录的说明

当前使用的get操作本身就是根据指定internalId获取单条记录的接口。如果需要批量获取Job记录,需改用search操作,构造JobSearchBasic等搜索条件实现批量查询。

内容的提问来源于stack exchange,提问作者Kompal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 12:20:00