ShvIA

Docs · Start here

Authentication and API keys

Requests are authenticated with an API key. The same key works on the OpenAI-compatible, the Anthropic-compatible and the native endpoints. The few operations that manage the key itself need a session instead, as explained below.

The key#

  • Format: shvia_usr_<id>_<secret>, where <secret> is 32 letters and digits.
  • One key per account. Generating a new key replaces the old one at once. There are no named keys, no scopes and no per-key limits: limits belong to the account.
  • Valid for 365 days. The length is fixed. Generate a new key before it expires.
  • Shown once. The server stores a hash of the key and a short preview, its first 16 and last 8 characters, never the whole key. If you lose the key, generate another.

Who can create a key#

Three conditions have to hold:

  1. You have an account. Public sign-up is closed unless the operator opened it; otherwise an administrator creates accounts.
  2. Your account group has API access. The operator enables it per group. A newly created account may sit in a group without it. In that case the request to generate a key is refused with 403 and a message that your group has no API permission. Ask your administrator to move you.
  3. You prove who you are. Generating a key needs your account password, and a session credential: a key cannot generate another key.

Create a key in the app#

Open your account, go to the API key section and choose to generate a key. Copy it when it is shown. Afterwards the app shows only a preview.

Create a key with the API#

Sign in to get a session token, then ask for the key. The password is required again here.

bash
curl -s "$SHVIA_URL/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{ "email": "you@example.com", "password": "your password" }'
json
{
  "status": "success",
  "message": "Login realizado com sucesso",
  "data": {
    "access_token": "1|…",
    "token_type": "Bearer",
    "expires_in": 86400,
    "user": { "id": 42, "email": "you@example.com", "work_group": "expert", "…": "…" }
  }
}

expires_in is in seconds: 86400 for a normal session, 2592000 when you also send "remember_me": true. user is the whole account record, with many more fields than shown. Treat it as informational: its fields are not a contract.

bash
curl -s "$SHVIA_URL/api/v1/auth/api-key/generate" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "password": "your password" }'
json
{
  "status": "success",
  "message": "API key gerada com sucesso. Guarde-a em local seguro, não será possível visualizá-la novamente.",
  "data": {
    "api_key": "shvia_usr_42_…",
    "expires_at": "2027-10-10T17:43:47.000000Z"
  }
}

Sign-in is rate limited to 10 attempts per minute per address, and 5 consecutive failures lock the account for 30 minutes.

Send the key#

Put the key in the Authorization header:

bash
curl -s "$SHVIA_URL/v1/models" \
  -H "Authorization: Bearer $SHVIA_API_KEY"

X-API-Key: <key> is accepted too. It is what the Anthropic SDK sends. If a request carries both an Authorization: Bearer token and an X-API-Key, the Bearer token is the one used, even when it is wrong.

bash
curl -s "$SHVIA_URL/api/v1/auth/verify" \
  -H "X-API-Key: $SHVIA_API_KEY"

Always use https://. A plain http:// request is redirected, and some clients drop the body of a redirected POST.

Check a key#

GET/api/v1/auth/verify#

Answers "is this credential good?" and nothing else, which makes it the right call for a health check in your integration.

json
{
  "valid": true,
  "via": "apikey",
  "user": { "id": 42, "full_name": "Maria Souza", "email": "maria@example.com", "work_group": "expert", "role": "user", "is_active": true },
  "checked_at": "2026-10-10T17:43:47+00:00"
}

via is apikey for an API key and sanctum for a session token. A bad credential never returns valid: false; it returns 401.

Revoke or rotate a key#

DELETE/api/v1/auth/api-key/revoke#

Revoking needs a session token, like generating. The old key stops working immediately. To rotate, generate a new key: the old one dies at the same moment, so update your secret store first and switch over quickly.

When a key is refused#

A key is refused with 401 when it is unknown, revoked or expired, when the account is deactivated, or when the account group no longer has API access. The body does not say which. On the /v1 endpoints the body uses the OpenAI error shape:

json
{
  "error": {
    "message": "Missing or invalid API key. Send it as \"Authorization: Bearer <key>\".",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  }
}

On the /api/v1 endpoints it is {"message": "Unauthenticated."}. See errors.

What an API key cannot do#

Some operations need a person at a screen, not a script. With an API key they answer 403, or 401 where noted:

  • generating or revoking a key;
  • saving or removing the keys of cloud providers, and connecting provider subscriptions (see providers and your own keys);
  • administration endpoints, which accept session tokens only and answer 401 to a key.

Session tokens#

The token returned by sign-in works on the same API endpoints as a key (on the /v1 endpoints only if your group has API access), and in addition on the account operations that a key cannot do, such as generating a key. It is meant for short sessions and for the apps. For scripts and services, use a key.

Good practice#

  • Read the key from the environment or a secret store.
  • Use one account per integration, so a leak is contained and usage is attributable.
  • If you log credentials for debugging, log only the shvia_usr_<id>_ part.
  • Keep the X-Request-Id header of failed calls. It identifies the request, and support can find it.