调用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
相关产品推荐
相关产品推荐

