How Caching Works
Every connected LinkedIn account has a finite action budget. A read that Curviate can answer from your tenant's own store does not spend that budget, so the same profile fetched twice in an hour costs one call, not two.
This page covers the two parameters that control it (mode and max_age), the default freshness thresholds, and the fields every cached read carries so you always know how old an answer is.
The store, in one paragraph
When a read reaches LinkedIn, Curviate keeps the result as a record in your tenant's store, stamped with the moment it was observed. The next read of the same resource compares that stamp against a freshness threshold for that kind of resource. Inside the threshold you get the stored copy and spend no budget. Outside it, Curviate fetches again and restamps. The store is tenant scoped: one workspace never reads another's records.
Two parameters
Every entity read accepts mode and max_age as query parameters.
max_age is the real mechanism, in seconds. Three of the four modes are presets over it.
| Mode | Meaning |
|---|---|
auto | The default. Serves a stored copy while it is within the resource's configured threshold, otherwise fetches. |
live | Always reaches LinkedIn. Equivalent to max_age=0. |
refill | Serves a stored copy at any age, and fetches only when this read has no stored copy yet. |
cache_only | Never fetches. Returns NOT_STORED when nothing is stored. |
A max_age you supply yourself outranks the auto, live and refill presets in both directions, so you can ask for something fresher or staler than the configured threshold. The largest value accepted is 31536000 (one year).
cache_only is the exception. It is not a max_age value but a separate guarantee: this read will not reach LinkedIn under any circumstances. Because of that, sending max_age alongside cache_only is refused rather than ignored, so a request can never quietly turn into the platform call the mode exists to prevent.
# Default. Stored copy if it is fresh enough, otherwise a live fetch.
curl -H "Authorization: Bearer $CURVIATE_API_KEY" \
"https://api.curviate.com/v1/acc_YOUR_ACCOUNT_ID/profiles/some-member"
# Force a fetch.
curl ... "https://api.curviate.com/v1/acc_YOUR_ACCOUNT_ID/profiles/some-member?mode=live"
# Accept anything up to an hour old.
curl ... "https://api.curviate.com/v1/acc_YOUR_ACCOUNT_ID/profiles/some-member?max_age=3600"
# Spend no budget under any circumstances.
curl ... "https://api.curviate.com/v1/acc_YOUR_ACCOUNT_ID/profiles/some-member?mode=cache_only"Default freshness thresholds
These are the values Curviate ships with. They are configuration, not constants: an operator can change a threshold without a deploy, so treat them as the current defaults rather than a contract.
| Resource | Default threshold |
|---|---|
| Person | 12 hours |
| Company | 3 days |
| Post | 12 hours |
| Comment | 12 hours |
| Job | 12 hours |
| Group | 14 days |
| Chat | 12 hours |
| Message | 12 hours |
The thresholds differ because the resources decay differently. A company's core record is stable for days. A post's reaction and comment counters move within hours, which is why a post is refreshed on the same schedule as a profile even though its body rarely changes.
A resource kind with no configured threshold is treated as zero, meaning every read of that kind reaches LinkedIn. Curviate fails toward spending budget rather than toward serving something stale as if it were current.
Knowing how old an answer is
You never have to guess whether a response came from the store. Every entity read carries two fields:
observed_at: when the data was actually seen on LinkedIn.source: whether this response came from the store or from a live fetch.
Over REST, a read answered purely from the store also carries an Age header. MCP has no headers, so an MCP client reads the same fact from observed_at and source.
{
"id": "...",
"observed_at": "2026-09-18T08:14:02Z",
"source": "store"
}When to reach for each mode
- Leave it on
autofor almost everything. It is the setting that protects your budget without letting an answer go quietly stale. - Use
livewhen you are about to act on the result and correctness matters more than budget: checking whether an invitation was accepted before sending a follow-up, or reading a headline you are about to personalise a message with. - Use
refillfor bulk enrichment, where having a record at all matters more than how fresh it is. It fetches once per resource and then stops spending. - Use
cache_onlywhen your agent must not act on the account at all, for example on an untrusted or exploratory code path. It is the only unconditional promise that a read will not touch LinkedIn. HandleNOT_STOREDas a normal outcome rather than an error.
A stored answer can carry less than a live one
Curviate does not retain every field it receives. Some are stripped before anything is written, so they were never in the store and cannot be served from it. A response with source set to the store may therefore omit a field that the same read returns when it goes live.
If your code depends on a field that is not present, re-read with mode=live rather than treating the absence as the resource not having a value.
Related
- Authentication & Accounts to get your API key and connect an account.
- Errors for
NOT_STOREDand the rest of the error surface. - The interactive API reference for the
modeandmax_ageparameters on each individual endpoint.