What it means
An API scope is a permission label carried by a credential. The credential says who you are; the scope says what that identity is allowed to reach. A key with directory:read and nothing else can call the catalogue endpoints and is refused everywhere else, no matter how valid it is.
Scopes exist because authentication and authorisation are different problems and collapsing them produces master keys. A single credential that can do everything the account can do is fine right up until it appears in a log file, a repository, a screenshot, or a subcontractor's laptop. At that point the only question that matters is how much the finder can do, and the answer is decided entirely by the scopes you attached months earlier.
Where the idea comes from
The vocabulary is OAuth 2.0. RFC 6749 section 3.3 defines the scope request parameter as a space-delimited, case-sensitive list of strings, and then declines to define the strings themselves: their meaning is up to the authorization server. There is no scope registry and there never has been.
That is why every product's scope names look different and all of them are legal. GitHub uses repo and user:email. Google uses full URLs. The common convention, and the one most readable at a glance, is resource:action, which sorts sensibly and makes an audit of a key's permissions a five-second job.
RFC 6750 supplies the other half: when a valid token lacks the required permission, the error code is insufficient_scope and the status is 403 Forbidden, not 401. The distinction is behavioural rather than aesthetic. 401 tells a client "I do not know who you are", and every reasonable client responds by re-authenticating. If you answer 401 to a scope failure, the client re-authenticates successfully, retries, gets 401 again, and loops. 403 tells it the truth: the credential is fine, the permission is not.
Six scopes, and what "sensitive" means
The Pinlyx Data API publishes six scopes, three of which are flagged sensitive. It is worth being precise about what that flag means, because it is not "this data is secret".
scrape:liveis sensitive because it costs. A live read goes to the platform right now and spends real upstream capacity shared with the rest of the product, which is why it has its own much smaller per-minute ceiling on every tier.leads:readis sensitive because of what it touches: company and contact data, and follower-graph exports. That is the scope you do not attach to a key that lives in a widget backend.tools:useis sensitive because it runs work rather than reading a row: valuation, follower audits, scoring.
The three unflagged scopes, directory:read, insights:read and research:read, read catalogued data at index-probe cost. Grouping by cost and reach rather than by secrecy is what makes the scope list actually useful for deciding what a given key should carry.
Least privilege, worked through
Least privilege is easy to agree with and easy to skip. The practical version is one key per job, with the scopes that job needs and nothing more:
- A public "is this handle real" widget backend needs
directory:read. Nothing else. If that key leaks, the finder can look up public catalogue rows, which is what the widget already shows the world. - A fraud review flow needs
directory:readplusscrape:live, because "does this account exist right now" is the whole point. It should not carryleads:read. - A prospecting job needs
leads:readand nothing else. If it leaks, nobody can burn your live-scrape budget with it. - An internal analytics notebook needs
insights:readandresearch:read, and should be a separate key from anything customer-facing so that revoking it at 2am breaks nothing a customer can see.
The compounding benefit is revocation. With one key per job you can kill a compromised credential immediately, because you know exactly what stops working. With one key for everything, revoking it is an outage, so it does not get revoked, so the compromise stays live.
Scopes are one gate of several
A scope answers one question, and a request that passes it can still be refused for reasons that have nothing to do with permissions. On the Data API there are five independent gates, each with its own status code and its own remedy, and telling them apart is what turns a support ticket into a two-minute fix. The table below lists all five.
The design principle behind it is that a refusal should be actionable. Compare a bare "403 Forbidden" with the actual response body: "This key does not carry the leads:read scope required by this endpoint." The second one names the missing permission, so the integrator fixes it in the panel without opening a ticket. Naming the scope in the message costs the API author one string and saves everyone else an afternoon.
Compute the scope set before you mint the key
Because the catalogue publishes a required scope on every endpoint, the minimum viable scope set for an integration is a calculation, not a discovery process: list the endpoints you call, take the distinct scopes, and mint a key with exactly those. Doing it in that order means you never go through the phase where somebody ticks every box "just to get it working" and then never comes back.
Related concepts
- Bearer token: the credential the scope is attached to.
- Rate limit: a different gate, with a different status code and a different fix.
- Cursor pagination: how the list endpoints a scope unlocks are actually walked.
- Webhook: the push side, where authorisation runs in the other direction.
How Pinlyx handles it
Every Data API key carries the scopes its plan allows, and every one of the 127 endpoints declares the scope it requires, so the mapping is published rather than discovered. Sensitive scopes are flagged as such in the catalogue. A refusal names the missing scope in the message and carries the stable code forbidden_scope so client code can branch on it, and keys can be further constrained to an IP allowlist for the cases where scope alone is not enough.