Baseplate Docs

Authentication

Two ways to authenticate against the API — and one permission model that ties them together.

Every request needs Authorization: Bearer <token>. The backend accepts two token shapes:

ShapeIssued byLifetimeTypical use
JWTPOST /login24 hoursThe admin dashboard, short-lived browser sessions
Personal Access Token (bod_…)POST /stores/{id}/api-tokensUntil you revokeLong-running scripts, integrations, automations

You can use either interchangeably on every endpoint. The auth middleware checks the bod_ prefix first and dispatches accordingly.

How a PAT relates to its user

A token is always issued on behalf of a real user. It can never do more than that user can. If the issuing user is later removed from the store, every token they issued stops working — there are no orphan tokens.

This matters for two reasons:

  1. Audit attribution. When a token writes an order, the change is recorded against the user who issued the token. Combined with the token name, you can trace any change back to a real human plus a piece of software ("Aline issued via the POS terminal").
  2. Restricted tokens via restricted users. If you want a truly read-only token even though you are the store owner, the cleanest pattern is to invite a teammate ("integrations bot") with limited permissions and issue the token in their name.

The scope bitmask

When you create a token you pick a set of permissions from the same checklist that appears on the team-permissions page. Internally each permission is one bit in an i64 mask.

At authentication time:

effective_permission = user.permissions & token.scope_bits

This is enforced at the lowest level in authz::has_permission, which means the store-owner shortcut also honors the mask. An owner-issued PAT with products.read only is genuinely read-only — the system does not silently expand it.

Bit math

Permission bits go up to position 61 today. JavaScript Number loses precision above 2^53, so if you're calculating scope masks in JS, use BigInt. The admin UI handles this for you when you check boxes.

Token format

bod_<43-char-base64>
  • The bod_ prefix is so GitHub-style secret scanning recognizes a leaked token and notifies you. Don't strip it.
  • The server stores only a sha256 hash. The raw value is shown to you exactly once on create or rotate.
  • Default expiry is "none" — tokens live until you revoke or rotate.

Rotation

Two ways to swap a token's value:

  • Rotate — same row, same name, same scope, brand-new raw value. Old value stops working immediately. Use this when a value has leaked.
  • Revoke + create new — different row, fresh last_used_at. Use this when the use case has changed.

Both flows return the new raw token once in the response body. After that, gone.

Revoking

curl -X DELETE \
  "https://bp.mobicms.com.br/stores/$STORE_ID/api-tokens/$TOKEN_ID" \
  -H "Authorization: Bearer $BP_TOKEN"

204 No Content means done. Any request carrying the revoked token returns 401 immediately — no grace period.

What happens on 401?

ReasonFix
Token revoked or expiredIssue a new one and update the client
Hash doesn't match anythingProbably a typo or stripped bod_ prefix
JWT past 24hRe-authenticate via /login

A 403 means the token is valid but its scope doesn't cover that endpoint. Check the permission column on the tokens table — the answer is usually obvious.

On this page