A 401 from Morning is almost always an expired token - the JWT lasts one hour. A 403 is a missing permission. How to tell them apart and build refresh that does not break.
Key takeaways
- A Morning JWT is valid for one hour. An integration that stores a token and reuses it the next day fails with 401 every morning.
- You obtain a token from `/account/token` using a key id and secret generated in the account area.
- 401 is identity. 403 is permission. If a fresh token did not fix it, it was never a token problem.
- Do not refresh on every call. Refresh against the expiry, slightly before it lapses.
- Log the full error body. Without it you cannot tell the two cases apart after the fact.
These are two different problems. 401 means the API does not know who you are - almost always an expired token, because a Morning JWT is valid for one hour. 403 means it knows exactly who you are and will not let you do that - a missing permission or an action your key is not allowed to perform. Refreshing the token fixes the first and does nothing for the second.
What is the practical difference between 401 and 403?
| | 401 Unauthorized | 403 Forbidden | |---|---|---| | Meaning | Not identified | Identified, not permitted | | Usual cause | Token expired or missing | Key not allowed for that action or document | | Check first | When the token was issued | What the key is configured for | | Does refreshing help | Yes | No | | Happens on a regular interval | Yes, the classic signature | No |
The clearest tell for an expired-token 401: the integration works perfectly, then fails at regular intervals, or fails reliably on the first call of the day.
Why the token expires after an hour
Morning does not use a static API key sent with every request. You generate a key id and secret in the account area, exchange them at `/account/token`, and receive a JWT. That JWT is what authenticates subsequent requests, and it is short-lived.
So an integration built on "I have a key, it works forever" rests on a false assumption. The key and secret are stable; the token they mint is not.
Building token refresh that does not break
- Store the token together with its expiry time, not just the string.
- Before each call, check whether only a few minutes remain, and refresh proactively if so.
- Handle a 401 by refreshing and retrying once. Retrying a genuine 401 in a loop is how accounts get locked.
- Do not mint a token per call. It doubles your request volume and looks like anomalous behaviour.
- Log the status code, the response body and the token issue time. That is what makes it diagnosable later.
What to check when it is a 403
- Does the key belong to the right account and the right business? On a multi-business account this is a common mistake.
- Is the specific action permitted for that key, or is the key read-only?
- Does the document you are addressing belong to a business the key covers?
- For a partner integration, are you sending the additional header partner keys require?
What actually goes wrong
- Token hard-coded or in a config file. Written once, worked for an hour, has failed ever since.
- Swallowed errors. Code that catches the exception without logging the body leaves you unable to tell 401 from 403.
- Sandbox versus production. A key generated in one environment will not authenticate in the other.
- Unsynced server clock. If the clock drifts, expiry maths is wrong and tokens appear to expire early.
- Concurrent refresh. Several processes minting tokens at once overwrite each other.
Diagnosing it in three minutes
- Pull the status code and the response body of the failed call out of your logs. Without the body there is no diagnosis.
- Mint a fresh token by hand against `/account/token` and make one simple read call.
- Worked? It was token expiry. Fix the refresh mechanism, not the call.
- Failed with the same error? It is a permission. Check the key's configuration, not your code.
- Read worked but write failed? The key is limited to read scope.
What happens when several processes run at once
The problem people find last is concurrent refresh. Two processes notice the token expired at the same moment, both request a new one, and each overwrites the other's token in shared storage. The result is intermittent 401s that look inexplicable.
- With more than one process, hold the token in shared storage with a lock around the refresh.
- Do not build retry-until-success logic. One retry after a refresh, then fail explicitly with a log entry.
- Separate sandbox from production at the token-storage level too, not only at the key level.
Related: automating Morning / Green Invoice, Israeli invoicing software API comparison, what a REST API is.
Sources
Frequently asked questions
How long is a Morning token valid?
The JWT is valid for one hour from issue. After that, any request using it returns 401. The key id and secret themselves stay constant - only the token they generate is short-lived.
I refreshed the token and still get an error. What does that mean?
If the error persists with a freshly minted token, you almost certainly have a 403 rather than a 401 - a permission problem, not an identity one. Check which business the key belongs to and which actions it is configured for.
Should I request a new token before every call?
No. It doubles your request count, slows the process, and looks like anomalous traffic. Store the token with its expiry and refresh only when a few minutes remain.
Why does the integration only fail in the morning?
That is the classic signature of a stored token. The job runs, gets a token, works; on the next day's run the token has long expired and the first request returns 401. The fix is refreshing against expiry rather than against run schedule.
Keep reading
Related service
Integrations
Make the systems you already pay for talk to each other.
About the author
Yehonatan Saadia
Freelance automation, web & MVP developer
I'm Yehonatan Saadia, a senior developer who builds business automation, custom websites, and MVPs for small and mid-sized companies across the US, Europe, and Israel. These guides come from real client work, not theory.
Work with meHave a project like this?
Tell me what you're trying to automate or build and I'll tell you the fastest reliable way to ship it.
