Parcels — Authentication

Every call to the Parcels API carries an OAuth 2.0 bearer token. This page covers getting one, the scopes that gate this vertical, and revoking a token.

Credentials

You need a client ID and client secret issued for your parcels vendor account. These are machine credentials — there is no browser redirect and no user login.

The client secret authenticates your whole account. Keep it server-side. Never ship it in a mobile app or browser bundle.

Getting a token

Kesk supports exactly one grant: client_credentials. Authenticate with HTTP Basic — client ID as username, secret as password — and send the grant type as JSON.

A successful response:

Read scope and expires_in from the response rather than assuming either. The values above are illustrative — the granted scope can be narrower than what you asked for, and the token lifetime is set server-side. Never hardcode a lifetime.

Using the token

Cache the token for its expires_in and refresh on a margin. The token endpoint does not emit rate-limit headers, but requesting a fresh token per call is wasteful and slow.

Scopes for Parcels

These are the scopes that gate this vertical. Request the narrowest set that does the job.

ScopeGrants
vendors:profile:read
Read the vendor profile
vendors:settings:read
Read vendor settings
vendors:locations:read
List and fetch locations
vendors:locations:write
Create, update and delete locations
vendors:webhooks:read
List and fetch webhooks
vendors:webhooks:write
Manage webhooks and rotate secrets
vendors:bots:read
List bots and read analytics
vendors:bots:write
Manage bots and rotate secrets
vendors:orders:read
List and fetch parcel orders
vendors:packages:read
Read package pricing rules
vendors:packages:write
Manage package pricing rules
vendors:units:read
List and fetch autonomous units
vendors:units:write
Register units, report status, issue machine credentials
vendors:promotions:read
List the coupons this vendor owns
vendors:promotions:write
Create, update and delete this vendor's coupons
vendors:analytics:read
Read sales analytics and performance metrics
vendors:reviews:read
Read customer reviews

vendors:promotions:read gates vendorCoupons; vendors:promotions:write gates createVendorCoupon, updateVendorCoupon and deleteVendorCoupon.

vendors:analytics:read gates vendorSalesAnalytics and vendorPerformanceMetrics; vendors:reviews:read gates vendorReviews.

Scopes are matched exactly, or by a vendors:* wildcard on the : boundary. vendors:* is not a synonym for the coarse grant vendors — the wildcard grants the whole surface including the sensitive scopes the coarse grant withholds. Ask for it deliberately, not as shorthand.

The coarse grant vendors expands at issuance into the vendor surface's granular scopes, minus the eight the registry withholds by name — see OAuth 2.0 for the list. Those must be requested explicitly, or granted with a surface wildcard such as vendors:*.

On this vertical that exclusion covers issuing machine credentials, which vendors:units:write carries: provisionUnitCredential mints a machine credential and returns its plaintext secret, and a coarse consent must not silently include the ability to issue new hardware credentials for your fleet.

It also covers vendors:promotions:write: a coupon discounts your own revenue, and a coarse consent must not silently carry the ability to issue discounts against your business.

So a coarse vendors grant gives you every scope in the table above except the two vendors:units:* entries and vendors:promotions:write. Read the granted scope back from the token response regardless — it is the record of what you were actually given.

The complete scope registry is published as scopes_supported at accounts.kesk.app/.well-known/oauth-authorization-server. Note the host: that is the authorization server, not the API host, so the path does not resolve on api.kesk.app. The table above is the subset that gates this vertical; the registry also lists the scopes of every other surface. The scope in your token response remains the record of what you were actually granted.

Errors

SituationStatusBody
Missing Authorization header
401
{"title":"Unauthorized","message":"Authentication required.","status":401,"type":"unauthorized"}
Wrong client ID or secret
401
{"error":"invalid_client","error_description":"Client authentication failed."}
Unsupported grant_type
400
{"error":"unsupported_grant_type","error_description":"Only 'client_credentials' grant type is supported."}
Scope not on your registration
400
{"error":"invalid_scope","error_description":"Scope exceeds the client's registered grant: ..."}
Form-encoded body on /token
400
{"error":"Bad Request","message":"invalid content type ..., expect: application/json"}

Validation order is grant_type → client → scope, so a bad grant type is rejected before your credentials are checked.

Note the missing-header and wrong-content-type cases return plain text — parsing either as JSON will throw.

Revoking a token

The token endpoint takes JSON; the revoke endpoint takes application/x-www-form-urlencoded. Sending JSON here returns 415.

token_type_hint is optional and ignored, and access_token is the only value the endpoint will parse — any other string is a 400. Revocation applies to access tokens: passing a refresh token returns 200 and revokes nothing.

Revocation kills the entire token family, not just the string you passed — which is what you want when responding to a leak.

Per RFC 7009 the endpoint returns 200 even for an unknown token, so a 200 means "not valid any more" rather than "existed and was revoked". Treat it as idempotent and safe to retry.

To rotate a secret: issue and deploy the new credentials, drain in-flight work, then revoke the old token. Revoking first breaks every worker still holding it.

Reference

Generated reference — every type, field and argument, built from the live schema.

Built with