Skip to content
Developers · 2026-05-18 · 7 min read

API key or OAuth: which one do you actually need?

Trooply gives you two ways to authenticate from a server. Most people only need one of them. The most common support ticket we see is someone copying one credential into a place that expected the other — both look like sk_… tokens, and both authenticate, but to different doors. Here's how to tell them apart and pick the right one.

The two methods, one sentence each

  • Method 1 — API key. Mint a long-lived sk_… token in the portal, put it in Authorization: Bearer, call any /v1/* endpoint. Done.
  • Method 2 — OAuth client_credentials. Send your client_id + sk_live_… client_secret to /oauth/token, receive a short-lived JWT, put that in Authorization: Bearer for one hour, then refresh.

Both end up in the same Authorization: Bearer … header on the request that actually does the work. The difference is where the token comes from and how long it lives.

Method 1 — the API key path

The simplest possible auth: one credential, no token exchange, no refresh loop. You mint a key on /portal/api-keys, the portal shows it once, you paste it into your server's environment variable and never look at it again.

curl https://search.trooply.ai/v1/products/stats \
  -H "Authorization: Bearer sk_26186c8005c70a398b1de8dcb8a8a42b..."

Three properties make this the right default for most integrations:

  • Scopes. When you create a key you pick which actions it can perform — products:read, products:write, analytics:read, and so on. A bulk-indexing worker doesn't need access to analytics; a reporting cron doesn't need products:write. Narrowing the surface area means a leaked key does less damage.
  • Expiry. Optional but recommended. Set a key to expire in 90 days and the system stops accepting it on day 91 without anyone running a rotation script. The dashboard shows expiring keys two weeks ahead so you can rotate proactively.
  • Revoke + permadelete. Two-click takedown if a key leaks. Revoke flips is_active=false within seconds; Delete removes the row from the list once you're sure you don't need the audit history.

You can have many of these — one per service, one per environment, one per CI job. That's the point. The portal table sorts active keys to the top, so the freshly-minted one for your new integration is right under the header, and the rotated-out keys you haven't deleted yet sink to the bottom.

Method 2 — the OAuth path

Same destination, longer journey. You hold a client_id + sk_live_… client_secret pair (one per account, generated on signup). Each "session" of API work starts by exchanging that pair for a short-lived JSON Web Token:

# Step 1 — exchange credentials for a JWT.
curl -X POST https://search.trooply.ai/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=client_79467a93280c128c6c1091faf5e4b2c9 \
  -d client_secret=sk_live_b9a5d3a1...
# → { "access_token": "eyJhbGciOiJIUzI1NiI...", "expires_in": 3600 }

# Step 2 — use the JWT (valid for 1 hour).
curl https://search.trooply.ai/v1/products/stats \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiI..."

Why anyone would prefer this over the API key path:

  • Your SDK or framework requires it. A lot of OAuth-native client libraries (Spring Security OAuth2, the AWS SDK auth helpers, Postman's "OAuth 2.0" auth type, Azure DevOps service hooks) expect a token endpoint they can call themselves. They don't know what to do with a single Bearer token you hand them. If your tooling is built around RFC 6749, Method 2 is the path of least resistance.
  • Short-lived blast radius. The JWT expires after one hour. A token captured in a packet trace, a log line that wasn't redacted, or a stack-trace screenshot stops working on its own. Compare that to an API key, which lives until you actively revoke it.
  • Per-token observability. Each JWT has its own jti (token ID), iat (issued-at), and exp. If you're correlating which deploy or which job issued a particular call, the JWT carries enough metadata to do that without a side database.
  • Revocation as a side-effect of rotation. Rotate the client_secret and every currently-issued JWT keeps working until its hour is up — there's no global denylist to update, just a new secret that can't mint new tokens. For teams that want "stop new sessions but let inflight work finish," this is the cleaner shape.

The cost: every API call needs to either reuse a cached JWT or pay the round-trip to /oauth/token first. A naive client that fetches a fresh token per request will be five to ten times slower than one that caches.

Side by side

  Method 1 — API key Method 2 — OAuth client_credentials
Token formatsk_<64 hex>Credentials: client_id + sk_live_<64 hex>. JWT: eyJ…
LifetimeUntil you revoke / it expires (you choose)JWT: 1 hour. Credentials: until you rotate
Round-trips per call11 + occasional token refresh
ScopesPer-key (you pick at create time)Account-wide (whatever the plan allows)
Multiple credentials per accountYes — mint one per service / env / jobOne client_secret pair per account
RotationRevoke one key, create another. No coordination required across other services using their own keys.Rotate the shared client_secret. Every integration using it has to be updated simultaneously.
Best forBackend services calling a small set of endpoints, CI jobs, custom integrations.OAuth-native frameworks, security policies that require short-lived bearer tokens, audit-driven environments.
Worst atLong token lifetime — a leaked key works until you spot the leak.Single-shot scripts — the token-exchange overhead dominates a one-off curl.

A decision tree

  1. Are you calling Trooply from a backend service you control? → API key. Stop here.
  2. Are you using an SDK or auth library that wants you to point it at a token endpoint? → OAuth. The library will manage the refresh loop.
  3. Does your security review require server tokens to expire within an hour without manual intervention? → OAuth.
  4. Are you writing a one-off shell script? → API key. The OAuth round-trip isn't worth the readability cost.
  5. Are you in a regulated environment (PCI, HIPAA, SOC2-audited customer pipeline) that has specific opinions about bearer-token lifetime? → OAuth by default, and document the choice.
  6. None of the above? → API key. It's the simpler tool and the one we recommend.

The mistake we see weekly

The two credentials look almost identical and live on the same portal page. Both start with sk_. Both are 64 hex characters of token data. Both go in Authorization: Bearer — eventually. The only typographical difference is that a client_secret has live_ in the middle:

API key            sk_26186c8005c70a398b1de8dcb8a8a42bfd1e4bcb1a5d63c0d0aa821b6e98ba41
client_secret      sk_live_01129a6031ef6a89e0f47d6bc83af2c8dd04443a0b44773c880b5de5a1c592fe
                      ↑↑↑↑↑
                   the only visible difference

Pasting an API key into /oauth/token as the client_secret returns 401 unauthorized with the message "Invalid client credentials." It's not a bug — the API key isn't wrong, it's just answering a different question. The reverse mistake (using a client_secret directly as a Bearer token) also fails: the server expects a signed JWT or an API key in that header, not raw credentials.

Two ways to avoid the confusion:

  • Pick one method per integration and stick to it. Don't mix — if your indexing worker uses Method 1, don't also call /oauth/token from it just because the docs mention it.
  • Name your env vars after the credential type. TROOPLY_API_KEY for Method 1; TROOPLY_CLIENT_ID + TROOPLY_CLIENT_SECRET for Method 2. If you label everything as "secret" or "key" you'll eventually copy the wrong one.

Can you use both at the same time?

Yes — they're entirely independent. Different services on the same account can authenticate different ways. Your indexing worker can use an API key while your Postman collection uses the OAuth flow. Both will hit the same tenant data; both will count against the same plan limits; both will show up in the audit log labeled with the credential type that issued each call.

The one thing not to do is build a meta-integration that uses an API key to fetch a JWT. Two reasons: the endpoints aren't wired that way (the API key doesn't grant access to /oauth/token), and even if you got it working, you'd be paying the round-trip cost without getting the OAuth benefits.

Operational notes

  • Rate limits. Both methods share the same per-account budget. Switching from API keys to OAuth won't get you more requests per minute. Want isolated budgets? Mint one API key per integration — each has its own per-key rate ceiling.
  • Audit log. The activity log records every auth.token_issued (OAuth) and every API call by key prefix. If you ever need to answer "did service X authenticate after Y o'clock?", the audit row has the IP, the credential type, and the request_id of the call that followed.
  • 2FA does not affect server credentials. Two-factor auth (TOTP via Google Authenticator / Authy / 1Password / Microsoft Authenticator) gates only the human portal-login path. API keys and /oauth/token are unaffected by 2FA — a leaked server credential is fully usable without it. That's why we recommend short-lived OAuth tokens for high-stakes environments instead of relying on 2FA as a barrier.
  • Browser code is a different question. Neither method belongs in storefront HTML. For browser-side calls (search widget on a product page, an embedded "search by photo" button) use a public widget key (pk_live_…) bound to your Origin allow-list. See the public-key writeup for the full pattern.

If you're still unsure, start with the API key

It's faster to ship and easier to revoke. If you later discover your SDK insists on an OAuth token endpoint, you can switch — the two methods coexist, and there's no migration cost beyond changing what your service reads from its config. The reverse migration (OAuth → API key) is just as cheap.

The portal at /portal/api-keys shows both side-by-side with their own labelled curl examples. Try one, ship it, come back later if the other turns out to fit better.