qBittorrent icon indicating copy to clipboard operation
qBittorrent copied to clipboard

After upgrade to qBittorrent v4.5.0, now get BadRequest when I delete torrents through API

Open briankitt opened this issue 2 years ago • 31 comments

qBittorrent & operating system versions

qBittorrent: 4.5.0 x64, official build from website, no mods of any type Operating system: Windows 10 Pro 21H2 build 19044.2251

What is the problem?

This worked fine on prior version (unknown version, I last upgraded 2 months ago, so whatever version that was)

Here is my code. I've tried both POST and GET. It should be POST, per documentation, but I've tried GET to cover bases. Again, this worked perfectly yesterday. I upgraded to v4.5.0 today, and now it fails with 'BadRequest' every time.
I can delete this torrent just fine from the qBittorrent console

       var method = HttpMethod.Post;
       var url = "http://127.0.0.1:8080/api/v2/torrents/delete?hashes=a6383161d63323a7cef9606c0769374a3b047d54&deleteFiles=false";

        using (var myRequest = new HttpRequestMessage(method, url)
        {
            var myResponse = await httpClient.SendAsync(myRequest);
            if (myResponse.IsSuccessStatusCode)
            {
                using (var myStream = new MemoryStream())
                {
                    myResponse.Content.ReadAsStream().CopyTo(myStream);

                    return Encoding.Default.GetString(myStream.ToArray());
                }
            }
            else
            {
                return $"{myResponse.StatusCode} - {myResponse.ReasonPhrase} on {apiName}/{methodName}{query} method {method.Method}";
            }
        }

Steps to reproduce

It's an API that I wrote. I shared the code above. Not sure what else I can do?

Additional context

I'd be happy to share logs or any additional info if needed, please ask.

Log(s) & preferences file(s)

Nothing sensitive in here.
Config.zip

(N) 2022-11-29T00:47:08 - Enqueued torrent move. Torrent: "SomeFile". Source: "D:\BT\qInProgress". Destination: "D:\BT\Completed" (N) 2022-11-29T00:47:08 - Start moving torrent. Torrent: "SomeFile". Destination: "D:\BT\Completed" (N) 2022-11-29T00:47:09 - Moved torrent successfully. Torrent: "SomeFile". Destination: "D:\BT\Completed" (N) 2022-11-29T00:47:09 - Torrent download finished. Torrent: "SomeFile" (N) 2022-11-29T00:47:09 - Running external program. Torrent: "SomeFile". Command: d:\bt\PlexBatch\PlexWorker.Batch.exe TorrentFinished "SomeFile" "D:\BT\Completed\SomeFile" "SomeFile" "D:\BT\Completed" "a6383161d63323a7cef9606c0769374a3b047d54" "-" "a6383161d63323a7cef9606c0769374a3b047d54"

Inside of PlexWorker.Batch.Exe, I do the call above, which results in the BadRequest error There is NOTHING in the log indicating anything to do with my API delete.

briankitt avatar Nov 29 '22 00:11 briankitt

I have the same issue

depuytnl avatar Nov 29 '22 09:11 depuytnl

I have a similar issue. I have posted the details on case# 18132

grtgh avatar Dec 02 '22 01:12 grtgh

+1

Same problem

Getting Method not allowed

Switching to post worked for me

curl -d "hashes=$HASH&deleteFiles=true" -X POST "$QBITURL/api/v2/torrents/delete" --cookie "$COOKIE"

jobrien2001 avatar Dec 03 '22 15:12 jobrien2001

Starting with v4.5 many WebAPI endpoints were restricted to POST method only.

glassez avatar Dec 03 '22 16:12 glassez

Indeed, I had the same issue and changed it to a post with the parameters in the body.

Powershell code example: $hashesToDeleteString = "1a61bb4aeec95bdb9bb1082c67d24cc1bf66009c|1a61bb4aeec95bdb9bb1082c67d24cc1bf660045" $body = @{ "hashes"=$hashesToDeleteString "deleteFiles"="false" } Invoke-RestMethod -Uri "http://localhost:8080/api/v2/torrents/delete" -Method Post -Body $body

francisdidden avatar Dec 03 '22 17:12 francisdidden

I am using POST

On Sat, Dec 3, 2022 at 10:33 AM Vladimir Golovnev @.***> wrote:

Starting with v4.5 many WebAPI endpoints were restricted to POST method only.

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1336194151, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6BWUDG7BJYWIOBQAZLWLNY4RANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 03 '22 21:12 briankitt

url = 'api/v2/torrents/delete?hashes='+str(tor['hash'])+'&deleteFiles=true'

Sorry, how would I code this to use POST?

hstorey219 avatar Dec 03 '22 22:12 hstorey219

That depends on the language you use.

POST v GET is a 'method' used to transfer data from your app to the server (qBittorrent in this case). There are many methods, including PATCH, DELETE, etc. You would always specify a method in the transfer.

As a 'for example', I use C#. The method is the first parameter I pass. new HttpRequestMessage(method,

On Sat, Dec 3, 2022 at 4:10 PM Harry Storey @.***> wrote:

url = 'api/v2/torrents/delete?hashes='+str(tor['hash'])+'&deleteFiles=true'

Sorry, how would I code this to use POST?

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1336267384, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6FI2JMGHPFVQI5QYSTWLPALTANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 03 '22 22:12 briankitt

That depends on the language you use. POST v GET is a 'method' used to transfer data from your app to the server (qBittorrent in this case). There are many methods, including PATCH, DELETE, etc. You would always specify a method in the transfer. As a 'for example', I use C#. The method is the first parameter I pass. new HttpRequestMessage(method, On Sat, Dec 3, 2022 at 4:10 PM Harry Storey @.> wrote: url = 'api/v2/torrents/delete?hashes='+str(tor['hash'])+'&deleteFiles=true' Sorry, how would I code this to use POST? — Reply to this email directly, view it on GitHub <#18097 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6FI2JMGHPFVQI5QYSTWLPALTANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.>

Sorry I am using Python script

hstorey219 avatar Dec 03 '22 22:12 hstorey219

I've been using a POST, so that's not the issue. I changed from URL parms to Body parms. No difference. Still getting a BADMETHOD

On Sat, Dec 3, 2022 at 11:23 AM francisdidden @.***> wrote:

Indeed, I had the same issue and changed it to a post with the parameters in the body.

Powershell code example: $hashesToDeleteString = "1a61bb4aeec95bdb9bb1082c67d24cc1bf66009c|1a61bb4aeec95bdb9bb1082c67d24cc1bf660045" $body = @{ "hashes"=$hashesToDeleteString "deleteFiles"="false" } Invoke-RestMethod -Uri "http://localhost:8080/api/v2/torrents/delete" -Method Post -Body $body

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1336202282, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6GNI2MGK2JRURSRLW3WLN6YNANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 03 '22 22:12 briankitt

Here's an explanation of how a POST is done.

https://www.w3schools.com/python/ref_requests_post.asp

On Sat, Dec 3, 2022 at 4:15 PM Harry Storey @.***> wrote:

That depends on the language you use. POST v GET is a 'method' used to transfer data from your app to the server (qBittorrent in this case). There are many methods, including PATCH, DELETE, etc. You would always specify a method in the transfer. As a 'for example', I use C#. The method is the first parameter I pass. new HttpRequestMessage(method, … <#m_-7651487754727070193_> On Sat, Dec 3, 2022 at 4:10 PM Harry Storey @.> wrote: url = 'api/v2/torrents/delete?hashes='+str(tor['hash'])+'&deleteFiles=true' Sorry, how would I code this to use POST? — Reply to this email directly, view it on GitHub <#18097 (comment) https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1336267384>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6FI2JMGHPFVQI5QYSTWLPALTANCNFSM6AAAAAASN4SFUE https://github.com/notifications/unsubscribe-auth/AAXDH6FI2JMGHPFVQI5QYSTWLPALTANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.>

Sorry I am using Python script

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1336268099, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6CMF742E7AJBYGQIBDWLPA7RANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 03 '22 22:12 briankitt

Here's an explanation of how a POST is done. https://www.w3schools.com/python/ref_requests_post.asp On Sat, Dec 3, 2022 at 4:15 PM Harry Storey @.> wrote: That depends on the language you use. POST v GET is a 'method' used to transfer data from your app to the server (qBittorrent in this case). There are many methods, including PATCH, DELETE, etc. You would always specify a method in the transfer. As a 'for example', I use C#. The method is the first parameter I pass. new HttpRequestMessage(method, … <#m_-7651487754727070193_> On Sat, Dec 3, 2022 at 4:10 PM Harry Storey @.> wrote: url = 'api/v2/torrents/delete?hashes='+str(tor['hash'])+'&deleteFiles=true' Sorry, how would I code this to use POST? — Reply to this email directly, view it on GitHub <#18097 (comment) <#18097 (comment)>>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6FI2JMGHPFVQI5QYSTWLPALTANCNFSM6AAAAAASN4SFUE https://github.com/notifications/unsubscribe-auth/AAXDH6FI2JMGHPFVQI5QYSTWLPALTANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.> Sorry I am using Python script — Reply to this email directly, view it on GitHub <#18097 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6CMF742E7AJBYGQIBDWLPA7RANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.**>

Thank you!

hstorey219 avatar Dec 03 '22 23:12 hstorey219

thanks everyone. c#: moving from GET to POST worked for me: { bc = new FormUrlEncodedContent( { new KeyValuePair<string, string>("hashes", hash), new KeyValuePair<string, string>("deleteFiles", "false") }); hrm = hc.PostAsync("/api/v2/torrents/delete", bc).Result; result = hrm.Content.ReadAsStringAsync.Result; if (hrm.IsSuccessStatusCode) pass = true; }

grtgh avatar Dec 05 '22 01:12 grtgh

This still does not work for me, I've been doing a POST the entire time.

On Sun, Dec 4, 2022 at 7:17 PM grtgh @.***> wrote:

thanks everyone. c#: moving from GET to POST worked for me: { bc = new FormUrlEncodedContent( { new KeyValuePair<string, string>("hashes", hash), new KeyValuePair<string, string>("deleteFiles", "false") }); hrm = hc.PostAsync("/api/v2/torrents/delete", bc).Result; result = hrm.Content.ReadAsStringAsync.Result; if (hrm.IsSuccessStatusCode) pass = true; }

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1336593743, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6DVEF5YUDLKEKXWO63WLU7A3ANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 06 '22 00:12 briankitt

I would presume you donnt have authentication because you didnt pass a cookie?

Did you try a simple curl request just to test? or something like postman?

Im not familiar with the language.

jobrien2001 avatar Dec 06 '22 02:12 jobrien2001

I turned off authentication for localhost, so I don't need the cookie.

On Mon, Dec 5, 2022 at 8:19 PM jobrien2001 @.***> wrote:

I would presume you donnt have authentication because you didnt pass a cookie?

Did you try a simple curl request just to test? or something like postman?

Im not familiar with the language.

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1338634829, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6FYWMDCXQO7ZZ7FWT3WL2PERANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 06 '22 02:12 briankitt

Whats odd is that you were getting a bad request error...

I think we were all getting "method not allowed". Bad request would mean something is malformed?

I would start looking at running something simple like curl like i suggested before, just to test things out.

jobrien2001 avatar Dec 06 '22 02:12 jobrien2001

var method = HttpMethod.Post; var url = "http://127.0.0.1:8080/api/v2/torrents/delete?hashes=a6383161d63323a7cef9606c0769374a3b047d54&deleteFiles=false";

Doesn't seem like correct thing. Is query string allowed to be in URL when method is POST?

glassez avatar Dec 06 '22 03:12 glassez

First off, yes, I do it all the time, it's VERY common. Secondly, I've tried it both ways, query string and body.

On Mon, Dec 5, 2022 at 9:38 PM Vladimir Golovnev @.***> wrote:

var method = HttpMethod.Post; var url = " http://127.0.0.1:8080/api/v2/torrents/delete?hashes=a6383161d63323a7cef9606c0769374a3b047d54&deleteFiles=false ";

Doesn't seem like correct thing. Is query string allowed to be in URL when method is POST?

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1338695978, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6DKA2I767LA7RLL7H3WL2YMVANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 06 '22 03:12 briankitt

Did anyone get this working with python?

hstorey219 avatar Dec 10 '22 19:12 hstorey219

I use C#, I've never gotten it to work.

On Sat, Dec 10, 2022 at 1:16 PM Harry Storey @.***> wrote:

Did anyone get this working with python?

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1345365675, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6AKX5VXMC7IL7KXHD3WMTJHBANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Dec 10 '22 19:12 briankitt

same problem when I send with GET method that server return Method Not Allowed. When I change into POST then it said Bad Request, I already change to DELETE still bad request. I am using Postman with Authend is off

hoangduc67 avatar Dec 30 '22 17:12 hoangduc67

@qbittorrent/bug-handlers Could someone investigate it?

glassez avatar Dec 30 '22 17:12 glassez

Because qbittorrent try reading data from body instead of query. In my case, this code block is working:

import urllib
req = urllib.request.Request(
        url="http://host:port/api/v2/torrents/pause",
        method="POST",
        data=urllib.parse.urlencode({"hashes": "your_hashes"}).encode(encoding="utf8"),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )

and delete torrent:

req = urllib.request.Request(
        url="http://host:port/api/v2/torrents/delete",
        method="POST",
        data=urllib.parse.urlencode({"hashes": "your_hashes", "deleteFiles": "false"}).encode(encoding="utf8"),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )

D-Walter avatar Jan 14 '23 17:01 D-Walter

I still cannot get the DELETE to work, even making your suggested changes.

It still throws back 'BAD REQUEST' and does not delete.

Note, you are doing a PAUSE, not a DELETE.

Pause works fine for me.

On Sat, Jan 14, 2023 at 11:51 AM 与田祐希是真的天下第一 @.***> wrote:

Because qbittorrent try read data from body instead of query. In my case, this code block is working (Generated by Insomnia):

var client = new RestClient("http://host:port/api/v2/torrents/pause"); var request = new RestRequest(Method.POST); request.AddHeader("Content-Type", "application/x-www-form-urlencoded"); request.AddCookie("SID", "authkey"); request.AddParameter("application/x-www-form-urlencoded", "hashes=yourhash", ParameterType.RequestBody); IRestResponse response = client.Execute(request);

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1382870152, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6E3MCLA2LMI7OPEWN3WSLRS3ANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Jan 17 '23 03:01 briankitt

Works fine with built-in webUI.

glassez avatar Jan 17 '23 07:01 glassez

Both PAUSE and DELETE are working properly in my project. In fact, in new version webui api code 400 (BAD REQUEST) seems to denote any arguments error or even some server internal error (should be code 500).

If the C# code block does not work, please check my new reply of python version, the C# version is generated by Insomnia automatically (the request is properly working by sending request directly in Insomnia, which means the request is valid). The keypoint might be proper data encoding format.

Brian Kitt @.***> 于2023年1月17日周二 11:21写道:

I still cannot get the DELETE to work, even making your suggested changes.

It still throws back 'BAD REQUEST' and does not delete.

Note, you are doing a PAUSE, not a DELETE.

Pause works fine for me.

On Sat, Jan 14, 2023 at 11:51 AM 与田祐希是真的天下第一 @.***> wrote:

Because qbittorrent try read data from body instead of query. In my case, this code block is working (Generated by Insomnia):

var client = new RestClient("http://host:port/api/v2/torrents/pause"); var request = new RestRequest(Method.POST); request.AddHeader("Content-Type", "application/x-www-form-urlencoded"); request.AddCookie("SID", "authkey"); request.AddParameter("application/x-www-form-urlencoded", "hashes=yourhash", ParameterType.RequestBody); IRestResponse response = client.Execute(request);

— Reply to this email directly, view it on GitHub < https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1382870152 , or unsubscribe < https://github.com/notifications/unsubscribe-auth/AAXDH6E3MCLA2LMI7OPEWN3WSLRS3ANCNFSM6AAAAAASN4SFUE

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1384785241, or unsubscribe https://github.com/notifications/unsubscribe-auth/AH7NIHW7VWTVOZNQP7ZYI5DWSYF4VANCNFSM6AAAAAASN4SFUE . You are receiving this because you commented.Message ID: @.***>

D-Walter avatar Jan 17 '23 09:01 D-Walter

I believe I have identified the root cause of this problem.

The documentation clearly states that the delete call should be this: /api/v2/torrents/delete?hashes=8c212779b4abde7c6bc608063a0d008b7e40ce32&deleteFiles=false RE: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#delete-torrents That is incorrect.

The way this works is as follows. The URL must be /api/v2/torrents/delete Then the parameters MUST be form variables, NOT JSON, but form variables. If you sent it as form variables like this. then it works: hashes=8c212779b4abde7c6bc608063a0d008b7e40ce32 deleteFiles=false

On Tue, Jan 17, 2023 at 3:39 AM 与田祐希是真的天下第一 @.***> wrote:

Both PAUSE and DELETE are working properly in my project. In fact, in new version webui api code 400 (BAD REQUEST) seems to denote any arguments error or even some server internal error (should be code 500).

If the C# code block does not work, please check my new reply of python version, the C# version is generated by Insomnia automatically (the request is properly working by sending request directly in Insomnia, which means the request is valid). The keypoint might be proper data encoding format.

Brian Kitt @.***> 于2023年1月17日周二 11:21写道:

I still cannot get the DELETE to work, even making your suggested changes.

It still throws back 'BAD REQUEST' and does not delete.

Note, you are doing a PAUSE, not a DELETE.

Pause works fine for me.

On Sat, Jan 14, 2023 at 11:51 AM 与田祐希是真的天下第一 @.***> wrote:

Because qbittorrent try read data from body instead of query. In my case, this code block is working (Generated by Insomnia):

var client = new RestClient("http://host:port/api/v2/torrents/pause"); var request = new RestRequest(Method.POST); request.AddHeader("Content-Type", "application/x-www-form-urlencoded"); request.AddCookie("SID", "authkey"); request.AddParameter("application/x-www-form-urlencoded", "hashes=yourhash", ParameterType.RequestBody); IRestResponse response = client.Execute(request);

— Reply to this email directly, view it on GitHub <

https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1382870152

, or unsubscribe <

https://github.com/notifications/unsubscribe-auth/AAXDH6E3MCLA2LMI7OPEWN3WSLRS3ANCNFSM6AAAAAASN4SFUE

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub < https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1384785241 , or unsubscribe < https://github.com/notifications/unsubscribe-auth/AH7NIHW7VWTVOZNQP7ZYI5DWSYF4VANCNFSM6AAAAAASN4SFUE

. You are receiving this because you commented.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/qbittorrent/qBittorrent/issues/18097#issuecomment-1385103531, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXDH6G4BAWLNDDD7VB75RLWSZSEPANCNFSM6AAAAAASN4SFUE . You are receiving this because you authored the thread.Message ID: @.***>

briankitt avatar Jan 27 '23 01:01 briankitt

Thank you @briankitt for working this out. You have also solved this problem for me. The WebAPI documentation for pause and resume operations is also outdated and incorrect. Changing the pause/resume API calls to use form variables works.

eg:

HASHSTR="hash1|hash2|hash3"
curl -v -b "SID=${SID}" -X POST -F "hashes=${HASHSTR}" http://${QBTHOST}/api/v2/torrents/pause
curl -v -b "SID=${SID}" -X POST -F "hashes=${HASHSTR}" http://${QBTHOST}/api/v2/torrents/resume

Arathen avatar Feb 11 '23 04:02 Arathen

I have the same issue, I using Javascript ajax on tampermonkey of chrome, the browser console displays: 400 (Bad Request) my code is:

let url = '/api/v2/torrents/delete?hashes=xxxxxxxxxx&deleteFiles=true',
    xhrDelete = new XMLHttpRequest();
xhrDelete.open('POST', url, true);
xhrDelete.send();
xhrDelete.onreadystatechange = function () {
    if(xhrDelete.readyState == 4 && ((xhrDelete.status >= 200 && xhrDelete.status < 300) || xhrDelete.status == 304)){
        some code...
    }
}

update: Today I change my code to jQuery ajax, it worked and I don't know why.

Dreamray avatar Feb 14 '23 07:02 Dreamray