Skip to main content Skip to content

Authentication

JWT-based authentication for the ZopNight API. Login, signup, SAML SSO, token refresh, PATs, and request headers.

7 min read Last updated: 

ZopNight uses JWT-based authentication. All API requests (except auth endpoints) require a valid access token.

Overview

The authentication flow works as follows:

  1. Authenticate via login or SSO to receive an access token and refresh token
  2. List your organizations with GET /organisations, then call POST /auth/refresh with the chosen org to get an org-scoped access token
  3. Include the access token in the Authorization header for all API requests
  4. When the access token expires, use the refresh token to obtain a new one
  5. The gateway validates the JWT, extracts your organization context, and routes the request

Authentication Providers

ProviderHow to sign inDescription
Email/PasswordPOST /auth/login with "provider": "email"Standard email and password with OTP verification
GoogleGET /auth/google/callback?code=<authorization code>OAuth 2.0 with Google accounts
GitHubGET /auth/github/callback?code=<authorization code>OAuth 2.0 with GitHub accounts
SAMLPOST /auth/saml/initiateEnterprise SSO via SAML 2.0 identity providers

Login

POST /auth/login

Authenticate and receive tokens.

Email/password login
curl -X POST https://api.zop.dev/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@company.com",
"password": "your-password",
"provider": "email"
}'
Response
{
"data": {
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "dGhpcyBpcyBhIHJlZnJl..."
}
}

The login response does not list your organizations. Fetch them with GET /organisations, then select one by refreshing the token with its ID (see Token Refresh).

GET /auth/{provider}/callback

Complete a Google or GitHub sign-in.

Google and GitHub sign-in do not use POST /auth/login. After the provider redirects back with an authorization code, exchange it at the provider’s callback endpoint (google or github). The response has the same access_token / refresh_token shape as email login.

Request
curl "https://api.zop.dev/auth/google/callback?code=<authorization-code>"

List Organizations

GET /organisations

List the organizations you belong to.

Request
curl https://api.zop.dev/organisations \
-H "Authorization: Bearer <access_token>"

Signup

POST /auth/signup

Create a new user account.

Request
curl -X POST https://api.zop.dev/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "newuser@company.com",
"password": "secure-password",
"name": "Jane Doe",
"provider": "email"
}'

After signup, verify your email using the OTP sent to your inbox.

Email Verification

POST /auth/email/verify

Request a verification OTP.

Request
curl -X POST https://api.zop.dev/auth/email/verify \
-H "Content-Type: application/json" \
-d '{ "email": "user@company.com" }'
PATCH /auth/otp/validate

Validate the OTP code.

Request
curl -X PATCH https://api.zop.dev/auth/otp/validate \
-H "Content-Type: application/json" \
-d '{
"email": "user@company.com",
"otp": "123456"
}'

Token Refresh

Access tokens are short-lived. Use the refresh token to obtain a new access token before expiry. Pass claims.tenant-id to mint a token scoped to a specific organization; this is also how you switch organizations.

POST /auth/refresh

Exchange a refresh token for a new access token.

Request
curl -X POST https://api.zop.dev/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "dGhpcyBpcyBhIHJlZnJl...",
"claims": { "tenant-id": "3f6c2a9e-8b1d-4c7e-9f2a-1d5e6b7c8a90" }
}'
Response
{
"data": {
"access_token": "eyJhbGciOiJSUzI1NiIs...(new token)",
"refresh_token": "bmV3IHJlZnJlc2ggdG9r..."
}
}

SAML SSO

Enterprise organizations can configure SAML 2.0 single sign-on with their identity provider.

POST /auth/saml/initiate

Begin a SAML authentication flow.

Request
curl -X POST https://api.zop.dev/auth/saml/initiate \
-H "Content-Type: application/json" \
-d '{
"domain": "enterprise.com",
"callback_url": "https://api.zop.dev/auth/saml/callback",
"redirect_url": "<your app callback URL>"
}'

callback_url must be the gateway’s SAML callback. Returns a redirect URL to your organization’s identity provider. After authentication, the IdP redirects back with a SAML assertion that is exchanged for JWT tokens.

Create Organization

POST /organisations

Create a new organization for your account.

Request
curl -X POST https://api.zop.dev/organisations \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "name": "My Company" }'

Password Reset

POST /auth/password/forget

Initiate a password reset.

Request
curl -X POST https://api.zop.dev/auth/password/forget \
-H "Content-Type: application/json" \
-d '{ "email": "user@company.com" }'
PATCH /auth/password/reset

Complete the password reset.

Request
curl -X PATCH https://api.zop.dev/auth/password/reset \
-H "Content-Type: application/json" \
-d '{
"email": "user@company.com",
"otp": "123456",
"password": "new-secure-password"
}'

Logout

POST /auth/logout

Invalidate the current session.

Request
curl -X POST https://api.zop.dev/auth/logout \
-H "Content-Type: application/json" \
-d '{ "refresh_token": "dGhpcyBpcyBhIHJlZnJl..." }'

The session to invalidate is identified by the refresh token in the body.

Personal Access Tokens (PAT)

For CI pipelines, scripts, and other non-interactive clients, ZopNight supports Personal Access Tokens. PATs use the same Authorization: Bearer header as JWT access tokens but are validated against the auth service directly rather than through JWKS — they don’t expire on the same schedule as user JWTs.

PropertyValue
Prefixzn_pat_ followed by an opaque secret
Where to useAuthorization: Bearer zn_pat_...
Created fromThe ZopNight UI under Settings → API & MCP → API Tokens → + Create Token
OrganizationsRequired. The token is bound to the organizations you select; calls to any other org are refused with 403
PermissionsChosen per token and capped by your role in each org. Leave every permission unticked for a read-only token
Expiry30 days, 90 days, 1 year, a custom date, or no expiry
Using a PAT
curl https://api.zop.dev/resources \
-H "Authorization: Bearer zn_pat_xxxxxxxxxxxxxxxxxxxxxxxx"

Organization Context

Most JWTs include the active organization in the tenant-id or org_id claim, and that value is used to scope every request. The X-Org-Id header is a fallback only: it is read when the token carries no org claim. It cannot override an org claim. To switch organizations with a session token, refresh it with claims.tenant-id.

HeaderValueWhen to use
X-Org-IdThe target organization’s ID (a UUID)Only when the token carries no org claim. A token’s org claim always wins.

Do not put the organization in the URL path. Paths such as /orgs/{orgId}/resources return 404; call /resources and the gateway adds the org for you.

Unauthenticated Endpoints

A small number of endpoints intentionally bypass JWT validation, including the ones below. They use other mechanisms (HMAC signatures, body-carried JWTs, or no auth at all):

EndpointAuthenticationPurpose
POST /webhooks/githubHMAC-SHA256 signature in X-Hub-Signature-256GitHub webhooks for the deploy pipeline
POST /build-callbacks/{revisionID}JWT carried in the request body, signed with the build callback secretBuild runner reports back when an image is ready
/auth/* (any method)Varies (login, signup, refresh, OAuth callbacks)Authentication flows themselves
/oauth/*OAuth 2.1 client authenticationAuthorization server used by MCP clients

Request Headers

HeaderValueRequired
AuthorizationBearer <access_token | PAT>Yes (all endpoints except those listed above)
X-Org-IdAn organization IDOptional. Used only when the token carries no org claim
Content-Typeapplication/jsonYes (for POST/PUT/PATCH requests)
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·