Skip to main content
Solved

26R1 upgrade: long running process error on SO301000 page (RESTAPI)

  • September 23, 2026
  • 13 replies
  • 163 views

Forum|alt.badge.img+2

Since upgrading from 24R1 to 26R1, I’ve been getting the following errors from the RESTAPI using the SO301000 screen:

{"message":"A long operation is running for this screen in the current session. Please try again later."}

 

This didn’t happen at all in 24R1 and the small number long running processes that I’ve found on the System Monitor screen only run for a couple of seconds at the most.  Is there a way to prevent this error from occurring since I have no control over which schedules use PXLongOperation.StartOperation()?

Best answer by Steve Milner

@bpgraves, GetAwaiter().GetResult() isn't polling. It's the synchronous form of await: it blocks until the HTTP response for that one POST arrives, which for RecordCCPayment is the 202, and then your code moves on while the operation is still running. The cancellation token only caps how long you wait for that response; it doesn't touch the operation on the server, which is why the 409 didn't change. There's nothing to override. The polling is a separate loop of GETs to the Location URL, and it doesn't need async or await. Same pattern you're already using, on the same Client so it stays in the same session:

var resp = Client.PostAsync(RESTmethod, httpContent, cancelAfterDelay.Token).GetAwaiter().GetResult();
if (resp.StatusCode == HttpStatusCode.Accepted)
{
var statusUrl = new Uri(resp.RequestMessage.RequestUri, resp.Headers.Location);
while (Client.GetAsync(statusUrl).GetAwaiter().GetResult().StatusCode == HttpStatusCode.Accepted)
Task.Delay(1000).Wait();
}

The status URL answers 202 while the operation is running and 204 once it's done, so the loop exits on its own. If that POST is coming back 204 instead of 202, the operation was already finished and the loop won't run, which would mean the 409 is coming from a different call.

13 replies

JKurtz29
Varsity III
Forum|alt.badge.img+1
  • Varsity III
  • September 23, 2026

You might have just been getting lucky in the past. 

When you submit your call, you should get a response with a “Location” URL that you can test to see if the process is still running.  Here is a link in the Help that talks about this process (Status code 202):

https://help.acumatica.com/Help?ScreenId=ShowWiki&pageid=91bf9106-062a-47a8-be1f-b48517a54324


Forum|alt.badge.img+2
  • Author
  • Semi-Pro III
  • September 23, 2026

You might have just been getting lucky in the past. 

When you submit your call, you should get a response with a “Location” URL that you can test to see if the process is still running.  Here is a link in the Help that talks about this process (Status code 202):

https://help.acumatica.com/Help?ScreenId=ShowWiki&pageid=91bf9106-062a-47a8-be1f-b48517a54324

No, it’s definitely not luck since the same RESTAPI is used constantly throughout the day (every 5 minutes) and this RESTAPI error didn’t come up until right after the upgrade.  The only response I get is the JSON that I’ve provided...no Location URL.


Steve Milner
Varsity III
Forum|alt.badge.img+4
  • Varsity III
  • September 23, 2026

@bpgraves, your schedules aren't what's tripping this. In 26R1 the API checks for a running operation on SO301000 in the same session that sent the call. The key for that check comes from your integration's own login session. An operation started under any other session, scheduled ones included, can't set it off. Something your integration started on Sales Orders was still running when the next call came in.

That's also why you don't see a Location URL. Jerry has the right pattern. The URL comes back on the call that started the operation, not on the one that gets rejected. An action call (POST to /SalesOrder/<action name>) can answer 202 and keep running in the background. A PUT or DELETE waits for anything its own save kicks off before it responds, so those don't leave work behind. Log the status code on every call and look for a 202 just before each failure.

Once you find it:
- The same job sends the next call: poll that Location URL until it returns 204, then carry on.
- A different job shares the session: give each job its own sign-in and sign-out so one can't block the other.

A rejected PUT never gets as far as the save, so waiting a few seconds and resending is a safe backstop.


Forum|alt.badge.img+2
  • Author
  • Semi-Pro III
  • September 23, 2026

@bpgraves, your schedules aren't what's tripping this. In 26R1 the API checks for a running operation on SO301000 in the same session that sent the call. The key for that check comes from your integration's own login session. An operation started under any other session, scheduled ones included, can't set it off. Something your integration started on Sales Orders was still running when the next call came in.

That's also why you don't see a Location URL. Jerry has the right pattern. The URL comes back on the call that started the operation, not on the one that gets rejected. An action call (POST to /SalesOrder/<action name>) can answer 202 and keep running in the background. A PUT or DELETE waits for anything its own save kicks off before it responds, so those don't leave work behind. Log the status code on every call and look for a 202 just before each failure.

Once you find it:
- The same job sends the next call: poll that Location URL until it returns 204, then carry on.
- A different job shares the session: give each job its own sign-in and sign-out so one can't block the other.

A rejected PUT never gets as far as the save, so waiting a few seconds and resending is a safe backstop.

Are you referring to the URL that is making the call to RESTAPI?  I am logging my Request/Response only since the URL is created by me when I call the RESTAPI.  Like I said, the only response is the message I noted above.  I’m really not sure where the Location URL come in since I’ve never seen that when I get the long running process error. 

However, from what you’re saying, one of RESTAPI calls is the long running process that is still open when the next RESTAPI call is made.  Perhaps I need to submit each RESTAPI call with a timeout/cancellation?  I’m not sure is that’s the issue...currently I’m not setting one.


Steve Milner
Varsity III
Forum|alt.badge.img+4
  • Varsity III
  • September 23, 2026

@bpgraves, no, not your request URL. Location is a response header, not part of the body. Acumatica sends it with a 202 status, and only on the call that started the operation. The call that fails afterward never carries it. If your log only captures the response body, you won't see it even when it's there.

A timeout won't help. Cancelling on your side doesn't stop anything on the server. The operation runs on its own thread and finishes whether your client is still waiting or not. A timeout just sends your next call out sooner, into the same error.

Log the HTTP status code and the response headers on every call for a day. When the error shows up, look at the call just before it in the same session. If that one came back 202, that's your culprit. Take the Location URL from its headers and GET it until it returns 204, then send the next call.

If nothing in the run ever comes back 202, post the sequence of calls your job sends and I'll take another look.


Forum|alt.badge.img+2
  • Author
  • Semi-Pro III
  • September 23, 2026

@bpgraves, no, not your request URL. Location is a response header, not part of the body. Acumatica sends it with a 202 status, and only on the call that started the operation. The call that fails afterward never carries it. If your log only captures the response body, you won't see it even when it's there.

A timeout won't help. Cancelling on your side doesn't stop anything on the server. The operation runs on its own thread and finishes whether your client is still waiting or not. A timeout just sends your next call out sooner, into the same error.

Log the HTTP status code and the response headers on every call for a day. When the error shows up, look at the call just before it in the same session. If that one came back 202, that's your culprit. Take the Location URL from its headers and GET it until it returns 204, then send the next call.

If nothing in the run ever comes back 202, post the sequence of calls your job sends and I'll take another look.

I need to totally rewrite how I submit a sales order through the RESTAPI just to log these things that's you’re asking for.  Before I do this, I need some clarification.

Are you talking about RedirectLocation in the HttpResponse (C#)?  I’m still not understanding what Location you’re referring to. Is Location from the HttpResponseHeaders?

https://learn.microsoft.com/en-us/dotnet/api/system.net.http.headers.httpresponseheaders?view=net-10.0

I’m assuming the status code is here (please let me know if I’m wrong):

https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage?view=net-10.0

 

Also, what kind of polling are you referring to?  Are there any C# examples I can look at somewhere?


Steve Milner
Varsity III
Forum|alt.badge.img+4
  • Varsity III
  • September 23, 2026

@bpgraves, yes, HttpResponseHeaders. With HttpClient it's all on the HttpResponseMessage you already read the body from: response.StatusCode and response.Headers.Location (a Uri). RedirectLocation is the server-side ASP.NET property, so not that one. You shouldn't need a rewrite, just two more values where you log the response.

Check one thing before you change anything. On Sales Orders only an action call (a POST to /SalesOrder/<action name>) comes back 202. PUTs don't. If your code never POSTs to an action, there's no 202 to find and the cause is somewhere else.

Polling means a GET to that Location URL on the same HttpClient, so it stays in the same session, until it stops answering 202. 204 means the operation finished. Roughly:

var resp = await client.PostAsync(actionUrl, body);
if (resp.StatusCode == HttpStatusCode.Accepted)
{
    var statusUrl = new Uri(resp.RequestMessage.RequestUri, resp.Headers.Location);
    while ((await client.GetAsync(statusUrl)).StatusCode == HttpStatusCode.Accepted)
        await Task.Delay(1000);
}

Acumatica's own C# client works the same way. InvokeAction returns the Location header and WaitActionCompletion polls it every second until 204. Their console example wraps every action call in it:
https://github.com/Acumatica/AcumaticaRESTAPIClientForCSharp/blob/6.0/Acumatica%20REST%20API%20Console%20Application/RESTExample.cs#L155-L163


Forum|alt.badge.img+2
  • Author
  • Semi-Pro III
  • September 23, 2026

@bpgraves, yes, HttpResponseHeaders. With HttpClient it's all on the HttpResponseMessage you already read the body from: response.StatusCode and response.Headers.Location (a Uri). RedirectLocation is the server-side ASP.NET property, so not that one. You shouldn't need a rewrite, just two more values where you log the response.

Check one thing before you change anything. On Sales Orders only an action call (a POST to /SalesOrder/<action name>) comes back 202. PUTs don't. If your code never POSTs to an action, there's no 202 to find and the cause is somewhere else.

Polling means a GET to that Location URL on the same HttpClient, so it stays in the same session, until it stops answering 202. 204 means the operation finished. Roughly:

var resp = await client.PostAsync(actionUrl, body);
if (resp.StatusCode == HttpStatusCode.Accepted)
{
    var statusUrl = new Uri(resp.RequestMessage.RequestUri, resp.Headers.Location);
    while ((await client.GetAsync(statusUrl)).StatusCode == HttpStatusCode.Accepted)
        await Task.Delay(1000);
}

Acumatica's own C# client works the same way. InvokeAction returns the Location header and WaitActionCompletion polls it every second until 204. Their console example wraps every action call in it:
https://github.com/Acumatica/AcumaticaRESTAPIClientForCSharp/blob/6.0/Acumatica%20REST%20API%20Console%20Application/RESTExample.cs#L155-L163

I’ve always done a PUT (not a POST) to create the Sales Order.  So you’re saying that my status code will never be 202?  I’m just getting a 200 or a 409 (when I get the long operation error) and the Location is always null.


Steve Milner
Varsity III
Forum|alt.badge.img+4
  • Varsity III
  • September 23, 2026

@bpgraves, yes. A PUT never returns 202. Only an action call does. My replies assumed your job calls one at some point, and I never asked. If it only ever PUTs the order, those checks were a detour and your logs will never show a 202. The rest of my first reply still holds: this error can only come from something running on Sales Orders in your own session, not from schedules.

The 2025 R2 release notes for developers list this check as a change in that version, under "Handling of Long-Running Operations". Once a long-running operation has started for a form in a session, "Other requests to this form in this session will fail until the long-running operation completes." You never hit it in 24R1 because it arrived in 2025 R2.

In the stock 26 R1 code I found only a few things that can leave Sales Orders busy in a session through the API, plus whatever a customization adds. The actions SalesOrderCreateShipment, SalesOrderCreateReceipt, PrepareSalesInvoice and RecalculatePricesDiscounts run as long operations and answer 202 while they run. A PUT starts one in two cases, an external tax provider on the order's tax zone or a card payment sent with Authorize or Capture, but the PUT waits for it before answering, so those only matter if the request got cut off.

So the question is what your job does after the PUT. If it posts one of those four actions, that's your answer, and the fix is to poll the action's Location URL until it returns 204 before the next call. If it doesn't, post the sequence of calls it makes in one run, and say whether anything else shares that sign-in (same cookies or token), whether the orders use an external tax provider or a card payment, and whether there's a customization on that screen. Nothing to rewrite or log for any of that.

Meanwhile the rejected call saved nothing, so catch that error and resend after a pause. If it keeps coming, sign in fresh; a new session isn't blocked by the old one. A timeout on your side still won't help, because it doesn't stop anything running on the server.


Forum|alt.badge.img+2
  • Author
  • Semi-Pro III
  • September 24, 2026

My job creates Payments, RecordCCPayment, AppliedToOrder, and RecalculateExternalTax.  Out of the four, only RecordCCPayment and RecalculateExternalTax use POST.  Are you saying that one or two of those need to be polled?   Would not polling either one of these (even if I use a timeout) possibly cause the 409 status code error show up when the next SalesOrder is created?


Steve Milner
Varsity III
Forum|alt.badge.img+4
  • Varsity III
  • September 24, 2026

@bpgraves, thanks for the additional information.

One of them. RecordCCPayment runs as a long operation, so that POST comes back 202 with a Location header while it's still running. GET that URL until it answers 204 before you send the next call. RecalculateExternalTax finishes inside the request and comes back 204, so there's nothing to poll there.

Yes. If RecordCCPayment is still running when your next call to that same screen arrives in the same session, that call gets the 409, and a timeout on the POST doesn't prevent it, because the operation keeps running on the server after your call returns. The lock is per screen, so which call takes the hit depends on where that POST goes: on the Payment entity it's your next Payment call, on SalesOrder it's the next SalesOrder call.

Add the poll right after RecordCCPayment and let the job run for a day. If a 409 still shows up after that, the call it lands on tells you which screen is still busy.


Forum|alt.badge.img+2
  • Author
  • Semi-Pro III
  • September 24, 2026

@bpgraves, thanks for the additional information.

One of them. RecordCCPayment runs as a long operation, so that POST comes back 202 with a Location header while it's still running. GET that URL until it answers 204 before you send the next call. RecalculateExternalTax finishes inside the request and comes back 204, so there's nothing to poll there.

Yes. If RecordCCPayment is still running when your next call to that same screen arrives in the same session, that call gets the 409, and a timeout on the POST doesn't prevent it, because the operation keeps running on the server after your call returns. The lock is per screen, so which call takes the hit depends on where that POST goes: on the Payment entity it's your next Payment call, on SalesOrder it's the next SalesOrder call.

Add the poll right after RecordCCPayment and let the job run for a day. If a 409 still shows up after that, the call it lands on tells you which screen is still busy.

I’m changed the calls for my POSTs to this now:

Client.PostAsync(RESTmethod, httpContent, cancelAfterDelay.Token).GetAwaiter().GetResult();

I thought the GetAwaiter() would do the polling but it doesn’t look like it since the 409 error is still occurring.  I can’t use the code example you gave me because my POST is in an SSIS package and await/async tasks are problematic.  How can I override the GetAwaiter() to do the polling without using async/await tasks?


Steve Milner
Varsity III
Forum|alt.badge.img+4
  • Varsity III
  • Answer
  • September 24, 2026

@bpgraves, GetAwaiter().GetResult() isn't polling. It's the synchronous form of await: it blocks until the HTTP response for that one POST arrives, which for RecordCCPayment is the 202, and then your code moves on while the operation is still running. The cancellation token only caps how long you wait for that response; it doesn't touch the operation on the server, which is why the 409 didn't change. There's nothing to override. The polling is a separate loop of GETs to the Location URL, and it doesn't need async or await. Same pattern you're already using, on the same Client so it stays in the same session:

var resp = Client.PostAsync(RESTmethod, httpContent, cancelAfterDelay.Token).GetAwaiter().GetResult();
if (resp.StatusCode == HttpStatusCode.Accepted)
{
var statusUrl = new Uri(resp.RequestMessage.RequestUri, resp.Headers.Location);
while (Client.GetAsync(statusUrl).GetAwaiter().GetResult().StatusCode == HttpStatusCode.Accepted)
Task.Delay(1000).Wait();
}

The status URL answers 202 while the operation is running and 204 once it's done, so the loop exits on its own. If that POST is coming back 204 instead of 202, the operation was already finished and the loop won't run, which would mean the 409 is coming from a different call.