Skip to content

Username and password

Sahl AI issues each organization a set of integration credentials. Your backend exchanges them for a clinician-scoped access token.

Log in

POST https://medipulse-staging.sahl.ai/users/login

Request

{
  "username": "<USERNAME>",
  "password": "<PASSWORD>",
  "identifier": "physician@hospital.sa"
}
Field Type Required Description
username string Yes Integration username issued by Sahl AI.
password string Yes Integration password issued by Sahl AI.
identifier string | null Recommended Email address of the clinician signed in to your EHR. Sahl AI links the session, consultation history and preferences to this clinician, and by default creates their profile under your organization the first time they appear. If your organization only allows pre-registered clinicians, an unknown email is rejected with 403. Send null only for service-level testing.

Response

200 OK

{
  "token": "<ACCESS_TOKEN>",
  "refresh_token": "<REFRESH_TOKEN>",
  "expires_in": 86400
}
Field Description
token Access token. Pass it to the embedded panel as access_token, or to the streaming API as the second subprotocol.
refresh_token Use it to get a new access token without sending the password again.
expires_in Token lifetime in seconds.

Errors

Errors return a JSON body of the form { "message": "..." }.

Status Meaning
401 The username or password is wrong, or a required field is missing.
403 The identifier is not registered and your organization only allows pre-registered clinicians, or the organization is inactive.
5xx Temporary problem on the Sahl AI side. Retry with exponential backoff.

Examples

curl -X POST 'https://medipulse-staging.sahl.ai/users/login' \
  -H 'Content-Type: application/json' \
  -d '{"username":"<USERNAME>","password":"<PASSWORD>","identifier":"physician@hospital.sa"}'
const res = await fetch(`${process.env.SAHL_API_BASE}/users/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    username: process.env.SAHL_USERNAME,
    password: process.env.SAHL_PASSWORD,
    identifier: clinician.email,
  }),
});
if (!res.ok) throw new Error(`Sahl login failed: ${res.status}`);
const { token, refresh_token, expires_in } = await res.json();
import os, requests

res = requests.post(
    f"{os.environ['SAHL_API_BASE']}/users/login",
    json={
        "username": os.environ["SAHL_USERNAME"],
        "password": os.environ["SAHL_PASSWORD"],
        "identifier": clinician_email,
    },
    timeout=15,
)
res.raise_for_status()
token = res.json()["token"]
using var http = new HttpClient();
var res = await http.PostAsJsonAsync(
    $"{Environment.GetEnvironmentVariable("SAHL_API_BASE")}/users/login",
    new {
        username = Environment.GetEnvironmentVariable("SAHL_USERNAME"),
        password = Environment.GetEnvironmentVariable("SAHL_PASSWORD"),
        identifier = clinicianEmail
    });
res.EnsureSuccessStatusCode();
var body = await res.Content.ReadFromJsonAsync<JsonElement>();
var token = body.GetProperty("token").GetString();

Refresh a token

POST https://medipulse-staging.sahl.ai/users/refresh-token

{
  "refresh_token": "<REFRESH_TOKEN>",
  "identifier": "physician@hospital.sa"
}

The response has the same shape as login. A new refresh_token replaces the old one, so store it.

When to refresh

Refresh before you open a new panel or start a new streaming session, not partway through a recording. A token that is valid when a session starts lasts for the whole consultation.