> ## Documentation Index
> Fetch the complete documentation index at: https://docs-sandbox.sparkle.fyi/llms.txt
> Use this file to discover all available pages before exploring further.

# Developer Onboarding

> Set up and test your Open Banking integration

Onboard as a developer by following the steps below:

## Onboarding Flow

<Steps>
  <Step title="Register Your Application">
    Start by registering your application in the sandbox environment:

    <Accordion title="Sample Request">
      <CodeGroup>
        ```bash cURL theme={null}
        curl -X POST ${AUTH_BASE_URL}/dcr \
          -H "Content-Type: application/json" \
          -d '{
              "username": "john.doe@example.com",
              "password": "password",
              "app_name": "My Open Banking App",
              "app_description": "A test application for Open Banking integration",
              "app_developer_name": "Fantastic Fintech Ltd",
              "app_developer_address": "Plot 1354, Okokomaiko, Lagos",
              "app_developer_contact_email": "john.doe@example.com",
              "app_developer_contact_phone": "+2348012345678",
              "redirect_url": "https://myapp.com/callback"
          }'
        ```

        ```javascript JavaScript theme={null}
        const response = await fetch('${AUTH_BASE_URL}/dcr', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            username: "john.doe@example.com",
            password: "password",
            app_name: "My Open Banking App",
            app_description: "A test application for Open Banking integration",
            app_developer_name: "Fantastic Fintech Ltd",
            app_developer_address: "Plot 1354, Okokomaiko, Lagos",
            app_developer_contact_email: "john.doe@example.com",
            app_developer_contact_phone: "+2348012345678",
            redirect_url: "https://myapp.com/callback"
          })
        });

        const credentials = await response.json();
        console.log('Client credentials:', credentials);
        ```

        ```python Python theme={null}
        import requests

        response = requests.post('${AUTH_BASE_URL}/dcr', json={
            "username": "john.doe@example.com",
            "password": "password",
            "app_name": "My Open Banking App",
            "app_description": "A test application for Open Banking integration",
            "app_developer_name": "Fantastic Fintech Ltd",
            "app_developer_address": "Plot 1354, Okokomaiko, Lagos",
            "app_developer_contact_email": "john.doe@example.com",
            "app_developer_contact_phone": "+2348012345678",
            "redirect_url": "https://myapp.com/callback"
        })

        credentials = response.json()
        print('Client credentials:', credentials)
        ```
      </CodeGroup>

      <Note>
        Username is unique, once taken cannot be re-used.
      </Note>
    </Accordion>

    #### Sandbox Environment Sample Response

    In sandbox, you'll receive immediate approval with full credentials:

    <Accordion title="Sample Response">
      ```json theme={null}
      {
        "status": "APPROVED",
        "client_credentials": {
          "client_id": "client_id",
          "client_secret": "client_secret",
          "client_name": "My Open Banking App",
          "redirect_url": "https://myapp.com/callback",
          "participant_id": "participant_id",
          "connection_id": "connection_id",
          "auth_base_url": "http://identity.sparkle.fyi",
          "api_base_url": "http://api.avidrone.fyi"
        }
      }
      ```
    </Accordion>

    #### Production Environment Sample Response

    In production, you'll receive a pending approval status with a registration token:

    <Accordion title="Sample Response">
      ```json theme={null}
      {
        "status": "PENDING_APPROVAL",
        "client_credentials": {
          "client_id": null,
          "client_secret": null,
          "client_name": "My Open Banking App",
          "redirect_url": "https://myapp.com/callback",
          "participant_id": null,
          "connection_id": null,
          "auth_base_url": "https://identity.sparkle.fyi",
          "api_base_url": "https://api.sparkle.fyi"
        }
      }
      ```

      <Note>
        After approval, use the username/password basic authorization to retrieve your credentials via the Get Client Credentials (GET `/dcr`) endpoint.
      </Note>
    </Accordion>
  </Step>

  <Step title="Retrieve Your Application Credentials (Production only)">
    In production environment, the call to register an application returns `PENDING_APPROVAL`. Once approved, call the GET /dcr API to retrieve application credentials:

    <Accordion title="Sample Request">
      <CodeGroup>
        ```bash cURL theme={null}
        curl -X GET ${AUTH_BASE_URL}/dcr \
          -H "Authorization: Basic base64(username:password)"
        ```

        ```javascript JavaScript theme={null}
        const response = await fetch('${AUTH_BASE_URL}/dcr', {
          method: 'GET',
          headers: {
            'Authorization': 'Basic base64(username:password)'
          }
        });

        const credentials = await response.json();
        console.log('credentials:', credentials);
        ```
      </CodeGroup>
    </Accordion>

    #### Production Environment Sample Response

    <Accordion title="Sample Response">
      ```json theme={null}
      {
        "status": "APPROVED",
        "client_credentials": {
          "client_id": "client_id",
          "client_secret": "client_secret",
          "client_name": "My Open Banking App",
          "redirect_url": "https://myapp.com/callback",
          "participant_id": "participant_id",
          "connection_id": "connection_id",
          "auth_base_url": "https://identity.sparkle.fyi",
          "api_base_url": "https://api.avidrone.fyi"
        }
      }
      ```
    </Accordion>
  </Step>

  <Step title="Test OAuth Flow">
    Test the complete OAuth authentication flow using different methods:

    <Accordion title="Sample Request">
      <CodeGroup>
        ```javascript PKCE Flow theme={null}
        // Generate PKCE challenge
        const generatePKCE = () => {
          const codeVerifier = crypto.randomUUID();
          const codeChallenge = btoa(codeVerifier);
          return { codeVerifier, codeChallenge };
        };

        // Redirect to authorization
        const { codeVerifier, codeChallenge } = generatePKCE();
        const authUrl = `${AUTH_BASE_URL}/oauth/authorize?` +
          `client_id=${credentials.client_id}&` +
          `redirect_uri=${encodeURIComponent(credentials.redirect_url)}&` +
          `response_type=code&` +
          `scope=customers.readonly accounts.list.readonly&` +
          `code_challenge=${codeChallenge}&` +
          `code_challenge_method=S256`;

        // Store code verifier for token exchange
        sessionStorage.setItem('code_verifier', codeVerifier);
        window.location.href = authUrl;

        // Handle callback and exchange code for token
        const handleCallback = async (code) => {
          const codeVerifier = sessionStorage.getItem('code_verifier');
          
          const tokenResponse = await fetch(`${AUTH_BASE_URL}/oauth/token`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'Authorization': `Basic ${btoa(`${credentials.client_id}:${credentials.client_secret}`)}`
            },
            body: new URLSearchParams({
              grant_type: 'authorization_code',
              code: code,
              redirect_uri: credentials.redirect_url,
              code_verifier: codeVerifier
            })
          });
          
          const tokens = await tokenResponse.json();
          console.log('Access token:', tokens.access_token);
          return tokens;
        };
        ```

        ```javascript Authorization Code Flow theme={null}
        // Step 1: Redirect to authorization
        const authUrl = `${AUTH_BASE_URL}/oauth/authorize?` +
          `client_id=${credentials.client_id}&` +
          `redirect_uri=${encodeURIComponent(credentials.redirect_url)}&` +
          `response_type=code&` +
          `scope=customers.readonly accounts.list.readonly&` +
          `state=${crypto.randomUUID()}`;

        window.location.href = authUrl;

        // Step 2: Handle callback and exchange code for token
        const handleCallback = async (code) => {
          const tokenResponse = await fetch(`${AUTH_BASE_URL}/oauth/token`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'Authorization': `Basic ${btoa(`${credentials.client_id}:${credentials.client_secret}`)}`
            },
            body: new URLSearchParams({
              grant_type: 'authorization_code',
              code: code,
              redirect_uri: credentials.redirect_url
            })
          });
          
          const tokens = await tokenResponse.json();
          console.log('Access token:', tokens.access_token);
          return tokens;
        };

        // Step 3: Refresh token when needed
        const refreshToken = async (refreshToken) => {
          const response = await fetch(`${AUTH_BASE_URL}/oauth/token`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'Authorization': `Basic ${btoa(`${credentials.client_id}:${credentials.client_secret}`)}`
            },
            body: new URLSearchParams({
              grant_type: 'refresh_token',
              refresh_token: refreshToken
            })
          });
          
          const tokens = await response.json();
          console.log('New access token:', tokens.access_token);
          return tokens;
        };
        ```

        ```javascript Device Code Authorization theme={null}
        // Step 1: Request device authorization
        const requestDeviceCode = async () => {
          const response = await fetch(`${AUTH_BASE_URL}/oauth2/device/code`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({
              scope: 'customers.readonly accounts.list.readonly',
              client_id: '${client_id}',
              target_connection_id: '${target_connection_id}'
            })
          });
          
          const deviceCode = await response.json();
          console.log('Device code:', deviceCode.device_code);
          console.log('User code:', deviceCode.user_code);
          console.log('Verification URI:', deviceCode.verification_uri);
          
          return deviceCode;
        };

        // Step 2: Poll for token
        const pollForToken = async (deviceCode, interval = 5000) => {
          const pollInterval = setInterval(async () => {
            try {
              const response = await fetch(`${AUTH_BASE_URL}/oauth/token`, {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: new URLSearchParams({
                  grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
                  device_code: deviceCode.device_code,
                  client_id: ${client_id}
                })
              });
              
              if (response.status === 200) {
                const tokens = await response.json();
                console.log('Access token received:', tokens.access_token);
                clearInterval(pollInterval);
                return tokens;
              } else if (response.status === 400) {
                const error = await response.json();
                if (error.error === 'authorization_pending') {
                  console.log('Waiting for user authorization...');
                } else if (error.error === 'authorization_declined') {
                  console.log('User declined authorization');
                  clearInterval(pollInterval);
                } else if (error.error === 'expired_token') {
                  console.log('Device code expired');
                  clearInterval(pollInterval);
                }
              }
            } catch (error) {
              console.error('Polling error:', error);
              clearInterval(pollInterval);
            }
          }, interval);
        };

        ```
      </CodeGroup>

      <Note>
        For device code authorization, display the instruction returned `obn_custom_metadata.consent_message` field in the response json
      </Note>
    </Accordion>

    #### Sample Response for Device Code Authorization

    <Accordion title="Sample Response">
      ```json theme={null}
      {
        "user_code": "{user_code}",
        "device_code": "{device_code}",
        "verification_uri": "https://idp.sparkle.fyi/device/verification",
        "interval": 5,
        "expires_in": 600,
        "obn_custom_metadata": {
          "consent_validation_method": "USSD",
          "consent_message": "please dial *556*{user_code}# and follow instruction to give consent to My Open Banking App",
          "consent_status": "PENDING_VALIDATION"
        }
      }
      ```
    </Accordion>
  </Step>

  <Step title="Test API Endpoints">
    Once you have an access token, test various API endpoints:

    <Accordion title="Sample Request">
      <CodeGroup>
        ```bash cURL theme={null}
        # Get accounts
        curl -X GET ${API_BASE_URL}/accounts \
          -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
          -H "idempotency_key: IDEMPOTENCY_KEY" \
          -H "signature: SIGNATURE"
        ```

        ```javascript JavaScript theme={null}
        // Test account information
        const accountsResponse = await fetch('${API_BASE_URL}/accounts', {
          headers: {
            'Authorization': `Bearer ${accessToken}`,
            'idempotency_key': $idempotency_key,
            'signature': $signature
          }
        });

        const accounts = await accountsResponse.json();
        console.log('Test accounts:', accounts);

        ```
      </CodeGroup>
    </Accordion>
  </Step>
</Steps>

## Next Steps

1. ✅ Build your application
2. ✅ Move to production

## Moving to Production

Once you've thoroughly tested in sandbox:

1. **Register in Production**: Use the same application details
2. **Wait for Approval**: Production registration requires review (24-48 hours). You will be informed once successfully registered. Alternative you can use the Get Client Credetnials API to get your client credentials
3. **Update Configuration**: Switch to production endpoints
4. **Test Again**: Verify everything works in production
5. **Go Live**: Deploy your application

<Note>
  Always test thoroughly in sandbox before moving to production. The sandbox environment is designed to catch issues early and prevent problems in production.
</Note>

<Button href="/guides/production-deployment" size="sm">
  Learn About Production Deployment
</Button>
