Febasidocs

Getting started

From "I have a tenant code" to "I'm holding a verified access token" — including how to create the first user.

This page takes you from "I have a tenant code" to "I'm holding a verified access token." It also covers how to provision the user you will log in with — login presupposes a user, and there are three legitimate ways to create one.

What you need

A tenant code

The unique slug of your organization — for the Febasi internal tenant this is febasi. You receive yours during onboarding.

A user account

Created via one of the three paths below. See Step 0 for the decision tree.

A password

Bcrypt-hashed at rest with 12 rounds. Sent only over HTTPS.

Step 0 — Prerequisites

Login presupposes both a tenant and a user. Both come from outside the API call itself.

A — Have an Auth project in Eved

The tenant is provisioned through the Eved platform — sign in to app.febasi.com.br, create a workspace, create a project of type Authentication, and submit the wizard. The tenantCode you pick becomes the value your application sends in every POST /login.

Full walkthrough: Eved → Getting started and Eved → Auth project.

B — Have a user

A fresh tenant has zero users. There are three legitimate ways to create one:

PathWhere it happensWhen to use it
Admin via Eved dashboardThe Users tab inside your Auth project in EvedOnboarding the first user of the tenant or one-off admin actions, no code.
In-product admin via JWTYour application, calling POST /api/v1/register with a tenant admin's JWTAn admin UI inside your product creates other users for the same tenant.
Server-side proxy via Client KeyYour tenant backend, calling POST /api/v1/register with a ck_* Client Key holding users:createA public signup page in your tenant's app. The browser never sees the key.

All three end up creating the same kind of user. The difference is the credential and the trust model around it.

Recommendations

✅ Do❌ Don't
Keep Client Keys server-side, scoped to users:create only when used for signup.Ship a Client Key in browser code, mobile bundles, or anything delivered to end users.
Run captcha, rate-limit, and fraud checks in your proxy before forwarding to /register.Hand admin JWTs to the frontend so it can call /register. Issue a narrow-scope Client Key instead.
Rotate Client Keys on schedule and on suspected leak.Treat /register as a public endpoint. It always requires a credential; the credential controls who can sign up where.
Restrict the Client Key to a tight CIDR via IP allowlist when the egress is known.Leave a high-scope key without an origin or IP allowlist when it ships to a browser-adjacent component.

The deep walkthrough for each path lives in Registration flow and Provisioning a tenant. Browser-direct signup (publishable key) is on the roadmap — until it ships, the server-side proxy is the canonical pattern for public signup.

Once you have a tenant and a user with a password, continue.

Step 1 — Log in

Send credentials

POST /api/v1/login returns an access token and a refresh token.

curl -X POST https://auth.febasi.com.br/api/v1/login \
  -H "Content-Type: application/json" \
  -d '{
    "tenantCode": "febasi",
    "identifier": "you@febasi.com.br",
    "password": "your-password"
  }'

Read the response

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "refreshToken": "8c9a3a48-7b9d-...",
    "expiresIn": 900,
    "user": {
      "id": "01HXY...",
      "email": "you@febasi.com.br",
      "username": null,
      "tenantId": "01HX0...",
      "tenantCode": "febasi"
    }
  }
}

The access token is a JWT signed with HS256. It already contains your roles, permissions, tenantId, and tenantCode, so most consumers do not need to call /me on every request.

Step 2 — Call an authenticated endpoint

curl https://auth.febasi.com.br/api/v1/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
{
  "success": true,
  "data": {
    "id": "01HXY...",
    "email": "you@febasi.com.br",
    "tenantCode": "febasi",
    "roles": ["super_admin"],
    "permissions": ["users:read", "users:create", "..."]
  }
}

If the token has expired, you will get:

{ "success": false, "error": "Token expired", "code": "TOKEN_EXPIRED" }

…which is your cue to refresh.

Step 3 — Refresh before the access token expires

curl -X POST https://auth.febasi.com.br/api/v1/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "8c9a3a48-7b9d-..." }'

The response is identical to /login — you get a brand-new access token and a brand-new refresh token. The old refresh token is revoked the moment the new one is minted (token rotation).

Sessions are atomic

Each successful /login opens a session backed by a refresh token. Tenants configure a max-concurrent-sessions limit; when you exceed it, the oldest session is revoked automatically so the new one fits. Read more in Sessions.

Step 4 — Log out

curl -X POST https://auth.febasi.com.br/api/v1/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "8c9a3a48-7b9d-..." }'

This explicitly revokes the refresh token. The access token will continue to work until its exp claim, but it cannot be refreshed any longer.

Where to go next

On this page