Auth
OAuth 2.0 token lifecycle (Laravel Passport) and logout
Revoke the credential used on this request (log out this device)
POST /api/v1/logout
Requires authentication (Bearer token) and the X-Organization-Id header.
Revokes the Passport access token AND its refresh token (or deletes the Sanctum token) that authenticated this request. Revocation is immediate: the next API call with the same token returns 401, and refreshing with its refresh token returns 400 invalid_grant.
Organization API tokens are managed in the admin panel and are rejected here with 400 invalid_token_type. Calling logout twice with the same token yields 401 on the second call (the token is already revoked).
Responses
| Status | Description |
|---|---|
| 200 | Token revoked |
| 400 | Credential cannot be revoked via logout: invalid_token_type (organization API token — manage it in the admin panel) or unsupported_token_type (unrecognised credential). |
| 401 |
Example request
curl -X POST "https://app.encryptinvoice.com/api/v1/logout" \
-H "Authorization: Bearer {{access_token}}" \
-H "X-Organization-Id: {{organization_id}}" \
-H "Accept: application/json"
Revoke ALL of the user's tokens (log out every device)
POST /api/v1/logout-all
Requires authentication (Bearer token) and the X-Organization-Id header.
Revokes every Passport access + refresh token and deletes every Sanctum personal access token belonging to the authenticated user — all devices and sessions. Same error contract as /api/v1/logout: organization API tokens get 400 invalid_token_type.
Responses
| Status | Description |
|---|---|
| 200 | All tokens revoked |
| 400 | Credential cannot be revoked via logout (invalid_token_type for organization API tokens, unsupported_token_type fallback). |
| 401 |
Example request
curl -X POST "https://app.encryptinvoice.com/api/v1/logout-all" \
-H "Authorization: Bearer {{access_token}}" \
-H "X-Organization-Id: {{organization_id}}" \
-H "Accept: application/json"
Current authenticated user
GET /api/v1/user
Requires authentication (Bearer token) and the X-Organization-Id header.
Returns the authenticated user. There is no dedicated /me; this is the closest equivalent.
Responses
| Status | Description |
|---|---|
| 200 |
Example request
curl -X GET "https://app.encryptinvoice.com/api/v1/user" \
-H "Authorization: Bearer {{access_token}}" \
-H "X-Organization-Id: {{organization_id}}" \
-H "Accept: application/json"
Start the OAuth2 Authorization Code + PKCE flow
GET /oauth/authorize
Open this URL in the system browser (NOT a WebView). Unauthenticated users are first redirected to /login and return here after signing in.
For first-party clients (the EncryptInvoice mobile app) the consent screen is auto-skipped: the browser is immediately redirected to redirect_uri with code and state. Third-party clients get a consent screen (HTTP 200) first.
PKCE is mandatory for public clients: send code_challenge (Base64url-encoded SHA-256 of a random 43–128 char code_verifier) and code_challenge_method=S256. Verify the returned state matches what you sent before exchanging the code.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
client_id |
query | string | yes | Public client UUID. |
redirect_uri |
query | string | yes | Must be registered on the client. Mobile uses https:///mobile/oauth2/callback (App/Universal Link) or the custom scheme com.encryptinvoice.mobile://oauth2redirect. |
response_type |
query | string | yes | |
scope |
query | string | no | Space-separated scopes. Default mobile scope string: organizations:read invoices:read invoices:write invoices:send quotes:read quotes:write quotes:convert customers:read customers:write contacts:read contacts:write expenses:read expenses:write |
state |
query | string | yes | Opaque CSRF token; echoed back on the redirect. MUST be verified by the client. |
code_challenge |
query | string | yes | Base64url(SHA-256(code_verifier)), no padding. |
code_challenge_method |
query | string | yes |
Responses
| Status | Description |
|---|---|
| 302 | Redirect to redirect_uri with ?code=...&state=... (consent auto-skipped for first-party clients). Unauthenticated users are bounced to /login first and return here after authenticating. |
| 200 | Consent screen (HTML) — shown for third-party clients only. |
| 401 | invalid_client — e.g. unknown client_id or unregistered redirect_uri. Returned directly (NO redirect), so a misregistered URI can never receive codes. |
Example request
curl -X GET "https://app.encryptinvoice.com/oauth/authorize" \
-H "Authorization: Bearer {{access_token}}" \
-H "X-Organization-Id: {{organization_id}}" \
-H "Accept: application/json"
Exchange an authorization code, refresh token, or (deprecated) password for tokens
POST /oauth/token
Token endpoint for all grants. Send application/x-www-form-urlencoded (JSON bodies are also accepted).
- authorization_code + PKCE (PRIMARY, mobile): public client — send
code_verifier, NO client_secret. The mobile app is a first-party client, so the issued token is user-scoped (organization_idstays NULL) — anyorganization_idsent is ignored, and the active org is chosen per request via theX-Organization-Idheader (see Organization context). For non-first-party (org-scoped) clients,organization_idbinds the token to that org; omitted = the user's primary org. - refresh_token: refresh tokens ROTATE — the old refresh token is revoked on use; always store the new pair. (Org-scoped only: re-send
organization_idto keep a non-primary binding; user-scoped tokens stay NULL.) - password (DEPRECATED): legacy/web integrations only — mobile MUST use PKCE.
After logout (or any revocation) the access token immediately fails with 401 and its refresh token is revoked (refresh attempts return 400 invalid_grant).
Responses
| Status | Description |
|---|---|
| 200 | Token issued |
| 400 | invalid_grant — expired/used authorization code, bad code_verifier, or a rotated/revoked refresh token (incl. after logout). For the deprecated password grant: wrong credentials. |
| 401 | invalid_client — unknown client_id, or a confidential client without its secret. Public PKCE clients never send a client_secret. |
Example request
curl -X POST "https://app.encryptinvoice.com/oauth/token" \
-H "Authorization: Bearer {{access_token}}" \
-H "X-Organization-Id: {{organization_id}}" \
-H "Accept: application/json"