Service account or OAuth for Search Console
The choice is not OAuth against service accounts. Both are OAuth 2.0. The question is whose Google account holds permission on the property, and whether a person has to be there.

A service account is not an alternative to OAuth for the Search Console API. Google states that all applications must use OAuth 2.0 for authorization when interacting with the Search Console API to access user data; a service account is an OAuth 2.0 client that authenticates as itself with a signed assertion rather than through a consent screen. The real decision is whose Google account holds permission on the property. Use a person's connection when the property belongs to a customer and they must grant access. Use a service account when the property is yours and the job has to run with nobody present.
All requests to the Google Search Console API must be authorized by an authenticated user.
Two scopes cover the Search Console API: https://www.googleapis.com/auth/webmasters grants read and write access, and https://www.googleapis.com/auth/webmasters.readonly grants read-only. Whichever credential type you choose, the token has to carry one of them, and a token issued for the read-only scope refuses writes such as submitting a sitemap. One neighbouring API behaves differently and is worth knowing about so you do not copy its setup: the Search Console Testing Tools API takes an API key rather than an OAuth token, because it reads public data.
Search Console grants access per Google account and per property, and every property a credential can act on is a property its account was granted. That is why the credential type is a secondary question. A service account is a Google account with an address of its own, so it can be granted access to a property the way a colleague is; a person's connection carries whatever that person holds. Neither one can act on a property nobody granted it, and no scope, key or library setting changes that.
There is a direct way to settle it rather than reasoning about it. Call sites.list with the credential in question and read what comes back: the properties listed are the ones it may act on, and each row carries a permissionLevel of siteOwner, siteFullUser, siteRestrictedUser or siteUnverifiedUser. A property missing from that list is a property the account has no access to, which is the answer to most "why does this work for me and not for the server" questions.

Google's 403 carries a reason code that names which of four failures happened. Read the reason before you change anything, because the fix is different in each case.

The submission itself can be undone. The saved address is the part that keeps acting on its own, because it is re-submitted every time you publish.
Market4 turns one release note into a changelog page, a blog post, a mail-out and a week of social posts — and then tells you which of them brought anyone back.
| Question | A person's OAuth connection | A service account |
|---|---|---|
| Who grants access | The person, through a consent screen | An owner adds the account's address to the property |
| Runs unattended | Yes, using a stored refresh token | Yes, no stored user consent involved |
| Suits a customer's own property | Yes; they consent to their own data | Rarely; it asks the customer to add your account as a user |
| When the person leaves the company | The connection dies with their account | Unaffected; the account belongs to the project |
| When you add a scope | Every existing connection needs reconnecting | Reissue the token with the new scope; no user involved |
| What to check when it fails | Whether consent still stands and which scopes it carried | Whether the address is still a user on the property |
A person's connection breaks in human ways. The individual leaves, revokes access from their Google account, or is removed from the property, and the integration stops with a 403 that names a permission problem. It also breaks silently on the day you add a scope: stored refresh tokens carry the scopes granted at consent time, so a new scope reaches an existing customer only when that customer reconnects. Detect the missing-scope message and ask for a reconnect rather than retrying.
A service account breaks in operational ways. Its key is a file that has to be stored, rotated and kept out of the repository; its access can be removed from a property by an owner who does not recognise the address and assumes it is stale; and because nobody logs in as it, nothing prompts a human when it loses access. The mitigation is monitoring rather than credentials: call sites.list on a schedule and alert when the property you depend on stops appearing.
A product that reports on its customers' own sites uses each customer's connection, because the data belongs to them and the consent has to be theirs. That is what Market4 does: the Google grant lives on the organisation, the property selection lives on the app, and disconnecting is the customer's action to take. An internal dashboard reporting on properties your own team owns is the opposite case, and a service account there removes a dependency on one employee's account remaining active.
Mixing the two is a legitimate answer as well. Some teams use a service account for the properties they control and user connections for everything else, accepting two code paths in exchange for not asking customers to add an unfamiliar address to their property. The cost is that the failure modes above are now both yours to monitor.
No. Google requires OAuth 2.0 for Search Console API access to user data, and a service account satisfies that requirement rather than bypassing it: it exchanges a signed assertion for an access token carrying the same scopes a user flow would carry. What it skips is the consent screen and the human, not the protocol. An API key is not a substitute either; that is what the separate Search Console Testing Tools API uses for public data.
Almost always because the two credentials authenticate as different Google accounts, and only one of them was granted access to the property. Call sites.list with the server's credential and see whether the property appears; if it does not, an owner needs to add that account in Search Console. The second candidate is scopes: a token minted without a Search Console scope answers 403 with a message about insufficient authentication scopes.
Request webmasters.readonly if the integration only reads reports and inspects URLs, and webmasters if it also submits sitemaps or changes anything. Asking for write access you do not use makes the consent screen harder to accept and widens the blast radius of a leaked token. Adding the write scope later is possible but not free: existing connections keep the scopes they were granted until each one is reconnected.
Treat it as something that can stop working at any time, and handle the failure rather than predicting it. Google can invalidate a refresh token for several reasons, including the user revoking access, prolonged disuse or a policy change on the account. The practical design is to catch the authentication failure, mark the connection as needing a reconnect, and surface that state on the screen, so the person can fix it in one click instead of discovering a silent gap in the reports.

Search Console has no language dimension. On a site in more than one language you separate the data by page or by country, which means the URL structure you chose for translations decides what you can measure.