On this page

The Gurb API accepts two credentials:

  • a JWT bearer token, obtained by logging in — a person using the app;
  • a community API key, sent as X-Api-Key — an integration acting for one community (see API keys).
{
  "email": "admin@example.com",
  "password": "your-password"
}

The response contains a signed JWT. Send it on every subsequent request:

Authorization: Bearer <token>

The token payload carries userId, email, role, tenantId, and tenantSchema — so the token itself identifies which tenant you are acting in.

Lifetime: 7 days by default (JWT_EXPIRES_IN).

Two endpoints exist:

EndpointAuth requiredUse
POST /api/auth/refreshBearer tokenRotate while still valid
POST /api/auth/refresh-tokennone — send the refresh token in the bodyRotate after the access token expired

Most endpoints are tenant-scoped. The API resolves which community you mean in this priority order:

  1. Custom domain — the request's hostname (highest priority)
  2. communityId in the URL — path, query, body, or the X-Community-Id header
  3. X-Tenant-Id header
  4. The JWT's tenant claim (lowest priority)

In practice, call tenant-scoped endpoints with the community in the path and send the tenant header as well:

Authorization: Bearer <token>
X-Tenant-Id: <tenantId>

Debugging tip. A 400 Tenant context required or 401 No authentication token provided does not reliably mean your credentials are wrong. Six routers are mounted at the API root, so a URL that matches no route falls into their middleware before Express can conclude "no such route" — a typo'd path answers 400/401 instead of 404. Confirm the route exists before you debug your token.

A token is not membership. Reads that work without a token (optionalAuth: tweets, events, blogs, albums, projects, projects 2, consultants and their lists) answer according to what the caller IS in that community:

CallerSees
APPROVED memberthe community's content; other members' drafts and hidden items stay hidden unless the member moderates that section
guest, or a signed-in non-memberonly items the community opted into guest preview, and only in a PUBLIC community whose plan includes the section
anyone elsean empty list, or 404

Something you may not see answers 404, never 403, so a response never confirms that a private item exists. The same applies to what hangs off an item: an event's or album's comments, likes and attendees are readable exactly when the item is.

StatusCodeMeaning
401UNAUTHORIZEDMissing, malformed, or expired token
403NOT_A_COMMUNITY_MEMBER / membership messageNot an APPROVED member of this community (left, banned, pending) — every write requires membership
403Forbidden + Missing permission: …A member, but lacking the permission. Editing or deleting someone else's content needs the section's manage or moderate permission; *:create only ever covers your own. Groups have no owner: editing any group needs groups:manage or groups:moderate
403RESTRICTION_BLOCKEDThe member is suspended or blocked from this action (posting, commenting, media, likes, RSVPs, events). Applies to API keys through their owner too
403FEATURE_NOT_AVAILABLEThe community's plan does not include this feature
403FEATURE_DISABLEDThe plan may include it, but the community's admin switched the section off (projects 2)
400TENANT_CONTEXT_REQUIREDNo community could be resolved from the request
400COMMUNITY_MISMATCHA communityId in the body disagrees with the community the request was authorized for
404…_NOT_FOUNDMissing, or not visible to you (see above)
409PROJECT_REQUEST_NOT_PENDING / JOIN_REQUEST_NOT_PENDING / INVITATION_NOT_PENDINGAlready decided — including by someone acting at the same moment. Exactly one approve/reject wins

Always read the response body: the 403s come from different checks and are fixed by different people (an admin, a moderator, the community admin's settings, or the billing owner).

FEATURE_NOT_AVAILABLE includes a featureKey field naming the feature that is off.

A community admin with api_keys:manage issues keys for integrations:

POST   /api/communities/{communityId}/api-keys     # create — the key is shown ONCE
GET    /api/communities/{communityId}/api-keys     # list (prefix only, never the key)
DELETE /api/communities/{communityId}/api-keys/{id}
  • Format gurb_<12 hex>_<secret>. Only a hash is stored; the full key is returned once, at creation. The gurb_<12 hex> prefix identifies it afterwards.
  • Scopes: read, write (default both). Optional expiresAt, up to 10 years.
  • Send it as X-Api-Key: <key> to the SDK surface, /api/sdk/…. The key is the community: it was pinned at creation, and nothing in the request can point it elsewhere.
  • A key acts as the admin who created it, and is checked the way that person would be, on every request: they must still be an APPROVED member, hold the permission (or own the content), and not be restricted. Demote or remove the owner and the key loses that reach with them.
  • Keys cannot manage keys: the key-management routes refuse API-key authentication.