API-created Notes don't (immediately) appear in Job Diary

We’re creating job notes from a headless backend integration using:

POST /api_1.0/note.json

Authentication is via API key. The notes are being created successfully and the data looks correct. We’ve verified this with:

GET /api_1.0/note.json?$filter=related_object_uuid eq '{job_uuid}'

The returned notes have the expected values:

  • active = 1

  • related_object = job

  • correct related_object_uuid

  • correct create_date / edit_date

The issue is that notes created this way don’t show up in the Job Diary for a client that already has that job open or previously loaded. On a stale client the notes do not necessarily appear on their own — we waited 20+ minutes with no change. They appear only via a fresh load, or a UI-originated Diary change.

If the job is already loaded in a client, the API-created note does not appear after:

  • passive / pull refresh

  • closing and reopening the job

  • navigating away and back

  • hard refresh with Shift-F5

  • closing and reopening the app window

This happens in both the web and desktop apps, even after waiting several minutes.

However, if the same job is opened for the first time in a fresh client — meaning an app instance that has not already loaded that job — the note appears immediately.

Also, on the stale client, if a user manually adds or deletes any note in that job’s Diary, all of the pending API-created notes appear at once.

Our interpretation is that the note data itself is fine, and this appears to be a client-side sync issue. A client that already has the job loaded does not seem to re-sync notes created through the API. A fresh load pulls them correctly, and a UI-originated Diary change appears to trigger the missing sync. So this looks less like a server-side feed cache issue and more like the client’s local replica/sync layer not receiving an event for API-created notes.

Is there a supported way for a note created through:

POST /api_1.0/note.json

to propagate to the Job Diary of a client that already has the job loaded?

For example, is there an event that the client normally subscribes to that API-created notes do not currently emit, or is there a job-level signal we can send from the backend to force the job Diary to re-sync?

We know that client.refreshJobDetails() works from an add-on iframe, but this integration is running headlessly on the backend, with no iframe or SDK context. We’re looking for the supported server-side equivalent for API-created notes.

Hi Chad,

That level of detail is really helpful.

We’ve identified an issue affecting some API key authenticated requests where the note is created successfully, but the follow-up diary refresh can be routed through the wrong API region.

This means the note can exist immediately (and be returned by the API), but the Job Diary view may keep showing stale diary data until another diary action or cache refresh causes it to reload.

There’s no issue with your note payload.

From the API side, POST /api_1.0/note.json doesn’t expose any extra parameters that would let you trigger a diary refresh at create time (it documents no query/path parameters, only the note record in the request body).

We’re working on a fix to ensure API-key requests are routed using the correct account region, and to make the Job Diary refresh correctly after API-created notes.

Thanks,
Cody

Hi Chad,

This should now be resolved. API-created notes should now appear in the Job Diary for jobs that are already open or previously loaded, without needing a manual diary action or fresh load.

Please let us know if you’re still seeing stale diary data after creating notes via the API.

Thanks,
ServiceM8 Team

I noticed an improvement before I seen this post, thought I was just getting lucky!

I’ll keep an eye on it though, thank you for the follow up

-Chad

We recently had the same problem, we found the fix for us was: The issue was regional routing. ServiceM8’s API seems to be split across regional servers, and the real-time diary push notification only fired if the API request come from the same region as your account.

We were calling the API from a cloud hosting provider with servers in the US. The note was being saved correctly (it would appear after a manual diary entry forced a refresh), but the live push notification never reached our client because it was being processed on the wrong regional server.

Make sure API calls come from a server in the same region as your SM8 account, for us that meant pinning our cloud functions to a UK region. Once we did that, notes appeared instantly every time.

Posting from localhost (in the UK) always worked fine. Same code, same API key: the only difference was where the HTTP request originated.

Posting just incase this helps anyone else

Hi Cody,

So the issue seemed to improve for awhile, then today it came back when I was working on something, but I noticed it was generating a stale error message in the console, so I traced it though.

The issue looks to be on the server side: GetDiaryLastModified and GetJSONDiary are returning inconsistent versions of the diary.

You were right that this is a stale-read issue. Here is the sequence we found:

Reproduction

A note is created with:

POST /api_1.0/note.json using an X-API-Key

Job: PAC35210
UUID: 050be09c-ca6d-46c7-a522-211dcae0a8db

The request returns HTTP 200 and the note is created correctly. However, the note does not appear in an already-open Job Diary until a UI edit triggers a reload.

What the client does

In plugin_job_card/job_card.entrypoint.js, version 115477, the refresh path is:

refreshDiary → loadDiaryWithKnownLastModifiedTime

  1. It calls GetDiaryLastModified first.

    Immediately after the note is created, that endpoint returns:

    intLastModified = 1782192476282

    So the metadata endpoint has already picked up the new note.

  2. The client stores that value and then calls GetJSONDiary with:

    &intLastModified=1782192476282

  3. GetJSONDiary then returns an older diary snapshot:

    intLastModified = 1782192440367

    That response does not include the new note, despite the request being made with the newer timestamp.

The client rejects the response because it is older than the timestamp it was told to expect:

Dropping stale diary response for Job:050be09c-… as expected lastModified 1782192476282 > 1782192440367

The result is that GetDiaryLastModified reflects the write immediately, while GetJSONDiary continues serving an older cached or replicated version of the diary.

Once the client has stored 1782192476282, every subsequent request asks for that value. If GetJSONDiary continues returning 1782192440367, the client keeps rejecting the response and the diary remains frozen until the content endpoint catches up. A UI edit appears to help only because it forces further refresh cycles.

That would also explain why the problem is intermittent: it depends on the timing between the write, the GetDiaryLastModified response, and the lag in GetJSONDiary.

Can the backend make GetJSONDiary read-after-write consistent with GetDiaryLastModified, or invalidate the diary content cache when a note is written? The content endpoint should not return an intLastModified older than the value the metadata endpoint has just reported.

That inconsistency appears to be the whole cause of the issue. Hope this helps

Thanks,
Chad

Ran into this again when deleting and re-importing some forms (formresponse.json creates and active:0 soft-deletes via X-API-Key) and noticed the old forms were still in diary and new forms were not showing up. I repainted the diary by posting a text note, and it updated - checked console and got a slightly different message I figured I’d add:

Skipping diary load for Job:019f4471-21b7-78b8-a980-dfddac6b82fb as expected lastModified 1783636412929 >= 1783636412929
Skipping diary load for Job:019f4471-21b7-78b8-a980-dfddac6b82fb as expected lastModified 1783637267340 >= 1783637267340

Seems like the content side eventually catches up — but by then every GetDiaryLastModified poll returns exactly the value the client already pinned, so the guard fires the other way: expected >= incomingthe client skips calling GetJSONDiary entirely. The rendered diary is still the old snapshot, but the client believes it’s current, so the recovered backend is never asked.

Even a full page reload minutes after the writes still shows the pre-write diary but note.json/formresponse.json GETs (the raw API) return the correct current data the whole time.

-Chad

Hi Chad,

I’m running into the same problem as you when attempting to post an attachment to the job diary. I’m just wondering if you’ve found a solution or heard back from anyone on the developer support team?

Thanks,

Stephanie

Stephanie,

Unfortunately no. I’ve tried every approach I could think of through the API, but everything API related runs into the same problem and nothing forces a repaint without a current state check. The issue affects attachments, posting / moving forms, renaming, notes - pretty much everything in the diary.

Unfortunately no matter the delay, command or approach taken, there’s a server-side state mismatch that the API can’t clear. We traced it in the client console: the job card checks two endpoints - GetDiaryLastModified (metadata) and GetJSONDiary (content). After an API write, the metadata side updates instantly but the content side still shows the old diary state. The client stores the newer timestamp, and once the two match it stops requesting diary content at all. A manual entry overwrites this, and everything actually works, but no API command that I’ve tried forces the repaint to make it display properly, and I believe i have tried every possibility at this point.

The fix needs to be on Servicem8’s side: either make GetJSONDiary read-after-write consistent with GetDiaryLastModified, or have the client only advance its stored timestamp after successfully applying content. No updates though other than what you see in this thread.

My current approach is I am planning on coding an autonomous staff member with an AI agent as the backend which will actually imitate keyboard/mouse interface with the app. If this issue persists through the completion of that, I may make a backdoor so we can inject commands - ie make a “#refresh” custom command which would have that agent do a manual diary note write / and then instantly delete the same note, since any manual update will fix it. Bit of an overengineered work around, but I will be working on that regardless, and I actually can’t think of any other work around.

Chad

1 Like

About what I expected, but still very frustrating. I guess we’ve just been lucky enough to avoid running into this issue until now, then.

Thanks for taking the time to respond, if you have any luck with your solution I’d love to know more if you’re happy to share.

Stephanie