2 Factor Authentication Flow

This document describes how a client makes requests to the bizAPIs system when Two-Factor Authentication (2FA) is required for Autoridade Tributária.

Overview

2FA Request Flow

Important: The entire 2FA flow is service-specific. Both the initial authentication request and the subsequent OTP submission request must be made to the same service endpoint. The session and credentials are bound to the specific service being accessed.

Phase 1: Initial Request with Credentials

Step 1: Client Sends Initial Request

The client makes a POST request to the orchestrator endpoint with credentials:

Endpoint: POST /v2/documents/{service}

Request Body:

{
  "user": "username",
  "password": "password",
  // IMPORTANT: Include all service-specific fields (e.g., year, month) in both initial and OTP requests
	"year": "2024"
}

Step 2: OTP Required Detection

After submitting credentials, the system checks if 2FA is required.

If OTP is not required, the flow skips directly to Phase 4 (Data Extraction and Response).

Phase 2: OTP Required Response

Step 3: Generate Second Request Token

When OTP is required, the service generates a second request token

Step 4: Return OTP Required Response

Response to Client:

{
  "otpResponse": {
    "secondRequestTokenId": "unique-token-uuid",
    "otpStatus": "OTP_REQUIRED",
    "requestId": "original-request-id"
  }
}

Phase 3: OTP Submission

Step 5: Client Sends OTP Code

The client makes a second request with the OTP code:

Endpoint: POST /v2/documents/{service}

Request Body:

{
  "otp": "123456",
  "secondRequestTokenId": "unique-token-uuid",
  // IMPORTANT: Include all service-specific fields (e.g., year, month) in both initial and OTP requests
  "year": "2024"
}

Key Points:

  • The secondRequestTokenId from the previous response is required
  • No credentials are needed in this request
  • The OTP code is provided by the user (received via SMS)
  • Both requests must be made to the same service endpoint - the OTP submission must target the same {service} as the initial authentication request

Step 6: Submit OTP to Portal

The service submits the OTP

Step 7: Verify Authentication

After OTP submission, the system verifies authentication

Phase 4: Data Extraction and Response

Step 8: Extract Data After Authentication

Once authenticated, the service proceeds with data extraction

Step 9: Return Final Response

The service returns the extracted data to the client

Success Response:

{
  "data": {
    "..."
  }
}

Error Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 0,
    "errorName": "AUTHENTICATION_FAILED",
    "fullStatusCode": "0000.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Authentication failed. Please verify your credentials and try again."
  }
}

Error Handling

OTP Attempt Validation

The system checks OTP attempts remaining

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 9,
    "errorName": "AUTHENTICATION_OTP_ATTEMPTS_EXCEEDED",
    "fullStatusCode": "0009.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Number of OTP attempts is about to be exceeded. Please authenticate manually to avoid account lockout."
  }
}

Session Expiration

Sessions can expire if:

  • The client takes too long to submit OTP (TTL default: 300 seconds)
  • The browser session is terminated
  • The service instance restarts

Error Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 6,
    "errorName": "AUTHENTICATION_SESSION_EXPIRED",
    "fullStatusCode": "0006.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Session expired or not found. Please authenticate again."
  }
}

Invalid OTP

If an invalid OTP is submitted, the authentication framework detects it during verification and returns:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 2,
    "errorName": "AUTHENTICATION_INVALID_CREDENTIALS",
    "fullStatusCode": "0002.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Invalid credentials provided. Please check your username and password."
  }
}

Mock Requests for Testing

The system includes a Demo Mode that allows testing the 2FA flow without actual portal authentication. This is useful for:

  • Integration testing
  • Client application development
  • Simulating various error scenarios
  • Understanding the complete request/response flow

Request Structure

All requests (both demo and production) use the same structure:

Special Password Values for Demo Mode

1. Successful Authentication (No OTP)

Password: Any value not matching special keywords (e.g., "success", "test123")

Request:

{
  "user": "test-user",
  "password": "success"
}

Response:

{
  "data": {
    "atInteracoesList": [
      {
        "tax": "IUC",
        "description": "Entrega da declaração",
        "date": "2025-10-13"
      }
    ],
    "atPiList": [
      {
        "title": "IUC",
        "status": "verde"
      }
    ],
    "atAlertsList": [
      {
        "descriptionName": "IRC",
        "description": "Data limite para entrega da declaração Modelo 25 de IRC relativo a 2025",
        "limitDate": "2025-10-13"
      }
    ],
    "requestId": "generated-uuid"
  }
}

2. OTP Required Scenario

Password: "otprequired" (case-insensitive)

Initial Request:

{
  "user": "test-user",
  "password": "otprequired"
}

Initial Response:

{
  "otpResponse": {
    "requestId": "generated-uuid",
    "secondRequestTokenId": "e67fe840-e6e5-4c98-a37e-1a79bca43035",
    "otpStatus": "OTP_REQUIRED"
  }
}

Second Request (with OTP):

{
  "otp": "123456",
  "secondRequestTokenId": "e67fe840-e6e5-4c98-a37e-1a79bca43035"
}

Final Response:

{
  "data": {
    "atInteracoesList": [...],
    "atPiList": [...],
    "atAlertsList": [...],
    "requestId": "generated-uuid"
  }
}

3. Error Scenarios

Each error scenario is triggered by a specific password value:

3.1. Session Expired (AUTHENTICATION_SESSION_EXPIRED)

Password: "sessionexpired"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 6,
    "errorName": "AUTHENTICATION_SESSION_EXPIRED",
    "fullStatusCode": "0006.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Session expired or not found. Please authenticate again."
  }
}

3.2. Invalid Credentials (AUTHENTICATION_INVALID_CREDENTIALS)

Password: "invalidcredentials"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 2,
    "errorName": "AUTHENTICATION_INVALID_CREDENTIALS",
    "fullStatusCode": "0002.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Invalid credentials provided. Please check your username and password."
  }
}

3.3. Problem During Authentication (AUTHENTICATION_FAILED)

Password: "problemduringauthentication"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 0,
    "errorName": "AUTHENTICATION_FAILED",
    "fullStatusCode": "0000.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Authentication failed. Please verify your credentials and try again."
  }
}

3.4. OTP Error in Process (AUTHENTICATION_OTP_ERROR)

Password: "otperrorinprocess"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 7,
    "errorName": "AUTHENTICATION_OTP_ERROR",
    "fullStatusCode": "0007.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Failed to submit OTP. An error occurred during the process."
  }
}

3.5. OTP Exception in Process (AUTHENTICATION_OTP_EXCEPTION)

Password: "otpexceptioninprocess"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 8,
    "errorName": "AUTHENTICATION_OTP_EXCEPTION",
    "fullStatusCode": "0008.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Failed to submit OTP. An exception occurred in the process."
  }
}

3.6. OTP Attempts Exceeded (AUTHENTICATION_OTP_ATTEMPTS_EXCEEDED)

Password: "numberofotpattemptsexceeded"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 9,
    "errorName": "AUTHENTICATION_OTP_ATTEMPTS_EXCEEDED",
    "fullStatusCode": "0009.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Number of OTP attempts is about to be exceeded. Please authenticate manually to avoid account lockout."
  }
}

3.7. Problem Reaching Authentication Page (AUTHENTICATION_PAGE_UNREACHABLE)

Password: "reachauthentication"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 3,
    "errorName": "AUTHENTICATION_PAGE_UNREACHABLE",
    "fullStatusCode": "0003.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Unable to reach authentication page. Please check your connection and try again."
  }
}

3.8. Page Timeout (TIMEOUT_OCCURRED)

Password: "pagetimeout"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 6145,
    "errorName": "TIMEOUT_OCCURRED",
    "fullStatusCode": "6145.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Operation timed out. The service is taking longer than expected to respond."
  }
}

3.9. Extract Data Error (EXTRACTION_FAILED)

Password: "extractdata"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 2049,
    "errorName": "EXTRACTION_FAILED",
    "fullStatusCode": "2049.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Failed to extract the requested data. Please try again later."
  }
}

3.10. Login Timeout (AUTHENTICATION_TIMEOUT)

Password: "logintimeout"

Response:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 1,
    "errorName": "AUTHENTICATION_TIMEOUT",
    "fullStatusCode": "0001.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Authentication timed out. The service is taking longer than expected to respond."
  }
}

Complete 2FA Flow Example (Demo Mode)

Scenario: User authentication requiring OTP

Step 1: Initial Authentication Request

curl -X POST /v2/documents/{service} \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-token" \
  -d '{
    "{ 
        "user": "test-user",
        "password": "otprequired"
      }"
    }

Step 1 Response:

{
  "otpResponse": {
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "secondRequestTokenId": "e67fe840-e6e5-4c98-a37e-1a79bca43035",
    "otpStatus": "OTP_REQUIRED"
  }
}

Step 2: Submit OTP Code

curl -X POST /v2/documents/{service} \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-token" \
  -d '{
    "{ 
    "otp": "123456",
    "secondRequestTokenId": "e67fe840-e6e5-4c98-a37e-1a79bca43035"
    }"
  }

Step 2 Response:

{
  "data": {
    "atInteracoesList": [
      {
        "tax": "IUC",
        "description": "Entrega da declaração",
        "date": "2025-10-20"
      },
      {
        "tax": "IRS",
        "description": "Entrega da declaração",
        "date": "2025-10-25"
      }
    ],
    "atPiList": [
      {
        "title": "IUC",
        "status": "verde"
      },
      {
        "title": "IRS",
        "status": "amarelo"
      },
      {
        "title": "Patrimonio",
        "status": "verde"
      }
    ],
    "atAlertsList": [
      {
        "descriptionName": "IRC",
        "description": "Data limite para entrega da declaração Modelo 25 de IRC relativo a 2025",
        "limitDate": "2025-11-15"
      },
      {
        "descriptionName": "IVA",
        "description": "Data limite para entrega da declaração Modelo (texto exemplo)",
        "limitDate": "2025-10-30"
      }
    ],
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Testing Checklist

Use demo mode to test the following scenarios:

OTP required flow

  • Password: "otprequired"
  • Expected: OTP response → submit OTP → data returned
  • This helps simulate the 2FA response flow

Session expiration (Code: 6)

  • Password: "sessionexpired"
  • Expected: AUTHENTICATION_SESSION_EXPIRED error

Invalid credentials (Code: 2)

  • Password: "invalidcredentials"
  • Expected: AUTHENTICATION_INVALID_CREDENTIALS error

OTP attempt limit (Code: 9)

  • Password: "numberofotpattemptsexceeded"
  • Expected: AUTHENTICATION_OTP_ATTEMPTS_EXCEEDED error

OTP submission errors (Codes: 7, 8)

  • Password: "otperrorinprocess" or "otpexceptioninprocess"
  • Expected: AUTHENTICATION_OTP_ERROR or AUTHENTICATION_OTP_EXCEPTION error

Network/timeout errors (Codes: 6145, 1)

  • Password: "pagetimeout" or "logintimeout"
  • Expected: TIMEOUT_OCCURRED or AUTHENTICATION_TIMEOUT error

Data extraction failure (Code: 2049)

  • Password: "extractdata"
  • Expected: EXTRACTION_FAILED error

Successful authentication without OTP

  • Password: other value
  • Expected: Data returned immediately

Note: In demo mode, any OTP code will be accepted as long as the correct secondRequestTokenId is provided.


Security Considerations

  1. Token Security: secondRequestTokenId is a UUID, preventing guessing attacks
  2. Session Isolation: Each requestId has its own isolated session
  3. Service Binding: The 2FA session is bound to the specific service endpoint - OTP submission must be made to the same service as the initial request
  4. OTP Attempt Limiting: System checks remaining OTP attempts before submission

2FA Error Messages Reference

This section lists all error messages that were added or modified as part of the 2FA feature implementation.

Authentication Errors

AUTHENTICATION_SESSION_EXPIRED (Code: 6)

HTTP Status: 200 (returned in response body)
Context: Occurs when attempting to submit OTP for a session that no longer exists
Response Example:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 6,
    "errorName": "AUTHENTICATION_SESSION_EXPIRED",
    "fullStatusCode": "0006.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Session expired or not found. Please authenticate again."
  }
}

Causes:

  • Client took longer than the TTL to submit OTP
  • Invalid secondRequestTokenId provided

AUTHENTICATION_FAILED (Code: 0)

HTTP Status: 200 (returned in response body)
Context: Generic authentication failure before OTP stage
Response Example:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 0,
    "errorName": "AUTHENTICATION_FAILED",
    "fullStatusCode": "0000.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Authentication failed. Please verify your credentials and try again."
  }
}

Causes:

  • Portal returned an error page after credential submission
  • Portal structure changed and elements could not be found
  • Unexpected exception during authentication process

AUTHENTICATION_INVALID_CREDENTIALS (Code: 2)

HTTP Status: 200 (returned in response body)
Context: Portal explicitly indicated invalid username or password
Response Example:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 2,
    "errorName": "AUTHENTICATION_INVALID_CREDENTIALS",
    "fullStatusCode": "0002.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Invalid credentials provided. Please check your username and password."
  }
}

Causes:

  • Incorrect username provided
  • Incorrect password provided
  • Invalid OTP code submitted

AUTHENTICATION_OTP_ATTEMPTS_EXCEEDED (Code: 9)

HTTP Status: 200 (returned in response body)
Context: Portal shows only 1 OTP attempt remaining
Response Example:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 9,
    "errorName": "AUTHENTICATION_OTP_ATTEMPTS_EXCEEDED",
    "fullStatusCode": "0009.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Number of OTP attempts is about to be exceeded. Please authenticate manually to avoid account lockout."
  }
}

Causes:

  • Multiple invalid OTP codes have been submitted
  • Portal security measure to prevent brute force attacks
  • User has 1 OTP submission attempt remaining

Resolution:

  • User MUST authenticate manually through the portal website
  • System will not submit the final attempt to avoid account lockout
  • After manual authentication, the counter will reset

AUTHENTICATION_OTP_ERROR (Code: 7)

HTTP Status: 200 (returned in response body)
Context: Generic error during OTP submission process
Response Example:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 7,
    "errorName": "AUTHENTICATION_OTP_ERROR",
    "fullStatusCode": "0007.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Failed to submit OTP. An error occurred during the process."
  }
}

Causes:

  • Portal returned unexpected response during OTP submission

AUTHENTICATION_OTP_EXCEPTION (Code: 8)

HTTP Status: 200 (returned in response body)
Context: Unexpected exception during OTP submission
Response Example:

{
  "errorResponse": {
    "requestId": "e18b757e-c99c-4705-8bdc-d35ee6dbbfdf",
    "errorCode": 8,
    "errorName": "AUTHENTICATION_OTP_EXCEPTION",
    "fullStatusCode": "0008.{serviceCode}.0003",
    "timestamp": "2026-01-14T14:10:07.291Z",
    "message": "Failed to submit OTP. An exception occurred in the process."
  }
}

Causes:

  • Unhandled exception in OTP submission code