للمطوّرين

المصادقة

أنشئ رمزًا، وأرسله مع كل طلب، وافهم قيدَي الوصول اللذين قد يكلفانك وقتًا طويلًا.

/v1 · إصدار العقد 1.0.0 · مرجع OpenAPI (openapi.json)


Getting a token

Tokens are minted by a Business owner (or anyone holding api:manage) in the PointLit app, under Manage → Settings → API tokens. It is the last row in that group, and it does not appear at all for staff who do not hold that permission, so if your shop owner cannot find it, they are signed in as somebody other than the Owner. Creating one asks for four things: a name, the permissions it may use, optional access restrictions, and an optional expiry date.

The secret is shown exactly once, on the screen that creates it. It is stored hashed and cannot be recovered: if it is lost, rotate the token and take the new one. A token looks like this, and the three parts are one opaque string; never split it, never re-case it, never trim it:

Token format
<tenantId>.<tokenId>.<secret>

The first eight characters of the middle part are shown in the app as the token’s prefix, so an owner can tell two tokens apart in a list and in the audit trail. The prefix is a label, not a credential.

Rotating issues a new secret and the old one stops working immediately. There is no overlap window, so schedule a rotation for a moment you can deploy the new value. The token keeps its identity, its name and its grants: nothing else about your integration changes. Revoking is permanent, and a revoked token can never be rotated back to life.

Authentication

Send the token as a bearer credential on every request. There is no login step, no session and no refresh: the token is the credential, from the first call to the last.

Every request
Authorization: Bearer <tenantId>.<tokenId>.<secret>

The base URL is the Business’s own PointLit address with /api in front of the version. The API is served on the same host as the shop’s app, on the subdomain or on a custom domain if the Business has connected one:

Base URL
https://<workspace>.pointlit.app/api/v1     # the workspace subdomain
https://shop.example.com/api/v1             # a connected custom domain

Either address serves the same Business and the same data. Use whichever the owner gives you, and read it from configuration rather than compiling it in, because a Business can connect a custom domain later.

What a refusal means

401 means the credential was not accepted, and it is deliberately generic: malformed, unknown, wrong secret, revoked and expired are indistinguishable, on purpose, because telling them apart would let an attacker confirm which tokens exist. There is no machine code on a 401. Check the header first, then ask the owner whether the token was revoked or has passed its expiry date.

403 means the opposite: you are authenticated, and this particular thing is not allowed. Three different situations wear it, and details.code tells them apart: the two access gates below, and a missing permission.

Access restrictions

A token can carry two optional allow-lists. Both are empty by default, and an empty list means “no restriction of this kind”.

Web origins

That is the single most common way a working integration stops working: an owner adds their storefront’s domain to the origin list “for safety”, and the storefront’s backend, which sends no Origin, is refused from the next request onwards, with api_token_origin_not_allowed.

IP addresses and CIDR ranges

An IP allow-list restricts the token to the addresses your servers call from. It is the right restriction for a server integration, with one caveat worth checking before you turn it on: the address PointLit sees is your egress address, not the address of the machine running your code. NAT, container networking, cloud egress pools and serverless platforms all change it, and several rotate it without notice. If you cannot name a stable egress range, leave the list empty rather than discover the problem during a sale.

If the API cannot determine the calling address behind its own proxy, an IP-restricted token is refused rather than admitted. That is api_token_ip_gate_unavailable, and it is an operator configuration problem, not yours.