Page 1 of 1

Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Mar 5日 23:07
by mark_libres

I'm working on integrating the Tuya API into my Python application to control smart devices. I'm able to successfully make GET requests, but I'm encountering issues when trying to make POST requests.

Whenever I attempt to send a POST request to the Tuya API, I consistently receive either a code 1004 error or a "sign invalid" message. I've verified that my client ID and client secret are correct, and my GET requests are working fine.

here is my Request class file

Code: Select all

import time
import hashlib
import hmac
import requests
import subprocess

class Request:

def __init__(self):

    self.ClientID = "**"
    self.ClientSecret = "**"
    self.BaseUrl = "https://openapi.tuyaus.com"
    self.EmptyBodyEncoded = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"


def headers(self, sign):
    
    return {
        "sign_method": "HMAC-SHA256",
        "client_id": self.ClientID,
        "t": self.tuyatime(),
        "mode": "cors",
        "Content-Type": "application/json",
        "sign": sign
    }



def tuyatime(self):
    return str(int(time.time() * 1000))


def access_token(self):
    URL = "/v1.0/token?grant_type=1"
    StringToSign = f"{self.ClientID}{self.tuyatime()}GET\n{self.EmptyBodyEncoded}\n\n{URL}"
    AccessTokenSign = hmac.new(self.ClientSecret.encode(), StringToSign.encode(), hashlib.sha256).hexdigest().upper()

    AccessTokenResponse = requests.get(f"{self.BaseUrl}{URL}", headers=self.headers(AccessTokenSign))
    return AccessTokenResponse.json()['result']["access_token"]

    pass


def sign(self, url, method):

    access_token = self.access_token()
    str_sign = f"{self.ClientID}{access_token}{self.tuyatime()}{method}\n{self.EmptyBodyEncoded}\n\n{url}"
    
    return [
        hmac.new(self.ClientSecret.encode(), str_sign.encode(), hashlib.sha256).hexdigest().upper(),
        access_token
    ]



def _sign(self, url, method, data):

    # Define the body

    print("data >> ", data)
    access_token = self.access_token()
    encoded_body = self.EmptyBodyEncoded
    
    if data != None:
        body_str = str(data).encode('utf-8')
        encoded_body = hashlib.sha256(body_str).hexdigest()


    print("encoded_body >> ", encoded_body)
    
    stringToSign = "\n".join([
        method, #HTTPMethod
        encoded_body, #Content-SHA256
        "", #Optional_Signature_key
        url #URL
    ])


    str_sign = f"{self.ClientID}{access_token}{self.tuyatime()}{stringToSign}"

    
    return [
        hmac.new(self.ClientSecret.encode(), str_sign.encode(), hashlib.sha256).hexdigest().upper(),
        access_token
    ]


def request_extra_params(self, url, method, data):

    sign, access_token = self._sign(url, method, data)

    headers = self.headers(sign)
    headers["access_token"] = access_token

    return {
        "headers" : headers
    }

    pass

def get(self, url, **kwargs):
    extra_params = self.request_extra_params(url, "GET", None)
    return requests.get(f"{self.BaseUrl}{url}", **extra_params, **kwargs)

def post(self, url, data, **kwargs):

    extra_params = self.request_extra_params(url, "POST", data)
    return requests.post(f"{self.BaseUrl}{url}", json=data, **extra_params, **kwargs)
    

here is the common file

Code: Select all

from classes.Request import Request
import json

_request = Request()

DEVICES = {
    "mark_table" : "***"
}


def set_state_mark_table(value = True):
    URL = f"/v1.0/devices/{DEVICES['mark_table']}/commands"
    return _request.post(URL, data=json.dumps({
        "commands" : [{"code":"switch_1","value":value}]
    }))
 	

her is the file to trigger the common file and Request class

Code: Select all

import common

value = common.set_state_mark_table(False)
print(value.text)

Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Mar 6日 10:23
by JamesG

You can first take a look at this article: https://developer.tuya.com/en/docs/iot/ ... 0q34cs2e5g.

Check your program and if there are still issues, please provide feedback on the forum.


Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Jun 14日 14:24
by Tran Manh Duy

I downloaded a Nodejs example code at:https://developer.tuya.com/en/docs/iot/ ... qza5d7iwkc
I successfully called GET method
But when I call POST method to send command control the device, it throws an error : Error: request api failed: sign invalid
This is source code:

Code: Select all

import * as qs from 'qs';
import * as crypto from 'crypto';
import { default as axios } from 'axios';

let token = '';

const config = {
  /* openapi host */
  host: 'https://openapi.tuyaus.com',
  /* fetch from openapi platform */
  accessKey: '',
  /* fetch from openapi platform */
  secretKey: '',
  /* Interface example device_ID */
  deviceId: '',
};

const httpClient = axios.create({
  baseURL: config.host,
  timeout: 5 * 1e3,
});

async function main() {
  await getToken();
  const data = await sendCommandControlDevice(config.deviceId);
  console.log('fetch success: ', JSON.stringify(data));
}

/**
 * fetch highway login token
 */
async function getToken() {
  const method = 'GET';
  const timestamp = Date.now().toString();
  const signUrl = '/v1.0/token?grant_type=1';
  const contentHash = crypto.createHash('sha256').update('').digest('hex');
  const stringToSign = [method, contentHash, '', signUrl].join('\n');
  const signStr = config.accessKey + timestamp + stringToSign;

  const headers = {
    t: timestamp,
    sign_method: 'HMAC-SHA256',
    client_id: config.accessKey,
    sign: await encryptStr(signStr, config.secretKey),
  };
  const { data: login } = await httpClient.get('/v1.0/token?grant_type=1', { headers });
  if (!login || !login.success) {
    throw Error(`fetch failed: ${login.msg}`);
  }
  token = login.result.access_token;
}

/**
 * GET method is success to fetch data
 */
async function getDeviceInfo(deviceId: string) {
  const query = {};
  const method = 'GET';
  const url = `/v1.0/devices/${deviceId}`;
  const reqHeaders: { [k: string]: string } = await getRequestSign(url, method, {}, query);

  const { data } = await httpClient.request({
    method,
    data: {},
    params: {},
    headers: reqHeaders,
    url: reqHeaders.path,
  });
  if (!data || !data.success) {
    throw Error(`request api failed: ${data.msg}`);
  }
}


/**
 * Fail when call POST method with Error: request api failed: sign invalid
 */
async function sendCommandControlDevice(deviceId: string) {
  const query = {};
  const method = 'POST';
  const url = `/v1.0/iot-03/devices/${deviceId}/commands`;
  const reqHeaders: { [k: string]: string } = await getRequestSign(url, method, {}, query);

  const { data } = await httpClient.request({
    method,
    data:  {
      "commands":[{"code":"switch_1","value":true}]
    },
    params: {},
    headers: reqHeaders,
    url: reqHeaders.path,
  });
  if (!data || !data.success) {
    throw Error(`request api failed: ${data.msg}`);
  }
}


/**
 * HMAC-SHA256 crypto function
 */
async function encryptStr(str: string, secret: string): Promise<string> {
  return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex').toUpperCase();
}

/**
 * request sign, save headers 
 * @param path
 * @param method
 * @param headers
 * @param query
 * @param body
 */
async function getRequestSign(
  path: string,
  method: string,
  headers: { [k: string]: string } = {},
  query: { [k: string]: any } = {},
  body: { [k: string]: any } = {},
) {
  const t = Date.now().toString();
  const [uri, pathQuery] = path.split('?');
  const queryMerged = Object.assign(query, qs.parse(pathQuery));
  const sortedQuery: { [k: string]: string } = {};
  Object.keys(queryMerged)
    .sort()
    .forEach((i) => (sortedQuery[i] = query[i]));

  const querystring = decodeURIComponent(qs.stringify(sortedQuery));
  const url = querystring ? `${uri}?${querystring}` : uri;
  const contentHash = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
  const stringToSign = [method, contentHash, '', url].join('\n');
  const signStr = config.accessKey + token + t + stringToSign;
  return {
    t,
    path: url,
    client_id: config.accessKey,
    sign: await encryptStr(signStr, config.secretKey),
    sign_method: 'HMAC-SHA256',
    access_token: token,
  };
}


main().catch(err => {
  throw Error(`error: ${err}`);
});


Please give me a solution or workaround to fix this issue to call POST method
Thanks


Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Jun 14日 14:52
by panda-cat

To facilitate request analysis, please send the most recent abnormal request result including tid, thank you.


Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Jun 14日 15:43
by Tran Manh Duy

Here is console log. Pls refer to the attach file

Code: Select all

PS C:\Users\Downloads\nodejs_demo.ts (origin111)\nodejs_demo> npm run test

highway@1.0.0 test
ts-node nodejs_demo.ts

Error: error: Error: request api failed: sign invalid
    at C:\Users\Downloads\nodejs_demo.ts (origin111)\nodejs_demo\nodejs_demo.ts:146:9
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
PS C:\Users\Downloads\nodejs_demo.ts (origin111)\nodejs_demo>

Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Jun 14日 16:29
by panda-cat

Please refer to the modification of the arrow marked part:

async function sendCommandControlDevice(deviceId: string) {
const query = {};
--> const body = {"commands":[{"code":"switch_1","value":true}]};
const method = 'POST';
const url = /v1.0/iot-03/devices/${deviceId}/commands;
--> const reqHeaders: { [k: string]: string } = await getRequestSign(url, method, {}, query,body);

const { data } = await httpClient.request({
method,
--> data: body,
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
if (!data || !data.success) {
throw Error(request api failed: ${data.msg});
}
}


Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Jun 14日 17:54
by panda-cat

To ensure the security of your account data, please protect your personal account key information. Please delete sensitive information in the question code or delete the old project and rebuild it.


Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Jun 14日 18:37
by Tran Manh Duy

Thank you very much for supporting me. It worked. I can send the POST method success
And I deleted personal info in the above question

By the way, Could you give me the document or example to create GET, PUT, POST, and DELETE methods to call Tuya API?


Re: Trouble with Tuya API POST request returning code 1004 or sign invalid

Posted: 2024年 Jun 14日 18:53
by panda-cat

This Service SDKs should have the information you need: https://developer.tuya.com/en/docs/iot/ ... 09frvx48db