NAV
Python C# Go Node.JS Ruby curl

Overview

Scroll down for code samples, example requests, and responses. Select a language for code samples from the tabs above or the mobile navigation menu.

Welcome to Citadel developer documentation.

Here you’ll find API docs, resources and guides for building integrations with BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. and our API.

Glossary

Citadel BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. is the client-side component that your users will interact with in order to link their accounts to Citadel and allow you to access their accounts via the Citadel API.

For each successful payroll provider connection BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. returns a public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. which represents a LinkA connection to a payroll provider used to retrieve payroll data. to a payroll provider.

Authentication

Every call to the Citadel API requires you to use your API keys, which consist of a Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. and an Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request.. Your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. will remain the same regardess of your environment, but you will get a different Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. for each environment. You'll be able to tell which key is used for which environment because the key will be prefixed with sandbox, dev or prod.

The Sandbox environment is where you'll do your development. You can get started by signing up at https://dashboard.citadelid.com/signup. Once you've verified your email address head to the API keys section to grab your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. and Sandbox Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request..

All API requests require the X-Access-Client-Id and X-Access-Secret headers which should contain your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. and Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. respectively.

Your API keys carry many privileges, so be sure to keep them secure! Do not share your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. or Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. with anyone and make sure to never use your API keys in the front end of your application.

API protocols

Citadel uses a REST API framework which leverages HTTP requests to communicate and HTTP response codes to indicate statuses and errors. All responses come in standard JSON format. Citadel's API is served over HTTPS TLS v1.2+ to ensure data privacy; HTTP and HTTPS with TLS versions below 1.2 are not supported. All requests must include a Content-Type of application/json and the body must be valid JSON.

Environments

We support three environments:

All API requests should be made to https://prod.citadelid.com/v1/ regardless of which environment you are using. Your Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. is what determines what environment your calls will be made to.

Dashboard

The Citadel Dashboard is the main place for accessing all information and configuration for your Citadel account. It's where you can view Logs and tasks, access your Customizations and Settings and view your API keys.

Logs and Tasks

The Dashboard has two sections that are useful to engineers when integrating Citadel or troubleshooting issues.

Logs

The Logs section of the Dashboard is where any calls made to the Citadel API will appear. You'll be able to see the time the call occurred, the environment it was called to, the status of the call and the endpoint that was called. Additionally if the data being returned relates to a specific payroll provider it will appear here as well. The date selection defaults to the last 7 days, so if you're trying to find older logs and they don't appear, adjust your "from" and "to" dates at the top.

Note: The "Sandbox mode" option on the left of the Dashboard determines what data is being shown for Logs. If you want to see logs for the sandbox environment make sure the "Sandbox mode" option is on. If you want to see development or production logs make sure the "Sandbox mode" option is off.

Tasks

The Tasks section of the Dashboard is where any TaskA process where a Link is used to pull data from a payroll provider. created will appear. You'll be able to see the status of the TaskA process where a Link is used to pull data from a payroll provider., any errors messages and payroll provider associated as well as the access_tokenA private token unique to a single Link. Used to fetch Link data. and whatever tracking_info you added. The date selection defaults to the last 7 days, so if you're trying to find older logs and they don't appear, adjust your "from" and "to" dates at the top.

Note: The "Sandbox mode" option on the left of the Dashboard determines what data is being shown for Logs. If you want to see logs for the sandbox environment make sure the "Sandbox mode" option is on. If you want to see development or production logs make sure the "Sandbox mode" option is off.

Get started

Get API keys

Your very first step in the Citadel development experience is acquiring your Sandbox API keys. Follow these steps to acquire them:

  1. Sign up at https://dashboard.citadelid.com/signup and verify your email.

  2. Find your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. and Sandbox Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. by clicking the Development > API keys menu item on the left. It's best to keep this page open in a different tab since you will need these values later.

Setup quickstart

Follow along with a video

Follow along as we walk you through setting up our Quickstart.

Watch the video

Step by step

The easiest way to get started with Citadel is to run the Quickstart locally. The Quickstart shows the entire workflow from initializing BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. to retrieving payroll data. It contains code examples in some of the most popular languages (C#, Go, NodeJS, Python and Ruby) and is open source. If you don't see your language, feel free to create an issue or open a pull request with an example in the language you love.

These instruction are designed to be run in a unix based environment (linux, MacOS, etc.). We are working hard to get a Windows compatible version to you soon. If you have Docker Desktop installed you can run the Quickstart using Docker Desktop (see step 3).

  1. Clone the Quickstart repository available here.You can do this by either downloading and extracting the zip file here or if you have git installed locally you can run:

    git clone https://github.com/citadelid/quickstart.git
    
  2. Copy the .env.example file to a new file in the same directory and name it .env. Update the values adding in the your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. and Sandbox Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. you noted in Get API keys. Uncomment the line with API_PRODUCT_TYPE that has the product you wish to view by removing the # at the beginning of the line.

    Note: Files that are prefixed with a "." are considered hidden files in a unix environment. If you are having trouble locating the .env.example file make sure you can see hidden files.

    API_CLIENT_ID=<Your Client ID here>
    API_SECRET=<Your Access key here>
    API_PRODUCT_TYPE=employment
    #API_PRODUCT_TYPE=income
    #API_PRODUCT_TYPE=admin
    
  3. From a terminal window run the make script for whichever language you'd like to demo in:

    make csharp_local
    
    make go_local
    
    make node_local
    
    make python_local
    
    make ruby_local
    
    make python_local
    

    If you have Docker installed you can run the same examples using Docker by running:

    make csharp_docker
    
    make go_docker
    
    make node_docker
    
    make python_docker
    
    make ruby_docker
    
    make python_docker
    

    Note: If you have an operating system that doesn't support makefiles you can always view the Makefile and run the commands for your language in a way your operating system will support.

  4. Visit http://localhost:5000 to view the Quickstart application. Click the "Connect" button and follow the instructions in BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API.. Your sandbox environment is designed to allow you to log in with multiple different credentials that give you different data scenarios. You can view the different credentials available in Sandbox credentials. Credentials for a successful payroll connection should be prefilled for the sandbox environment.

When you click "Done" at the end of BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. you will be able to see the data returned by the Citadel API.

Mobile quickstart

If you're trying to build Citadel into your mobile app we have quickstarts to help you there too. Citadel can easily be embedded into your mobile app using native WebView components. If you're developing an iOS mobile app you can view our iOS Quickstart. If you're developing an Android mobile app you can use our Android Quickstart.

Understand workflow

Console logs have been added to both the front end and back end of Quickstart to give you a good idea of the process an application runs through, but here's a basic explanation of the flow Quickstart (and most applications) follow. This flow involves a user, the application front end, the application back end and the Citadel API. The workflow can be separated into 3 parts.

  1. When the page loads, the applicant front end sends a request to the application back end for a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration.. This token is required to authorize the application front end to use BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API..

    const getBridgeToken = async () => {
        const response = await fetch(apiEnpoint + `getBridgeToken`, {
          method: 'get',
          headers,
        }).then((r) => r.json());
        return response;
      }
    
  2. The application back end sends a request to the Citadel API for a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. and sends the response to the application front end. It's necessary to request the bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. from the application back end because the Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. and Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. are required to authorize the request. Important: Never place or use your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. or Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. in your applications front end.

        public async Task<string> GetBridgeToken()
        {
          var body = "{ \"product_type\": \"" + productType + "\"," +
                     " \"tracking_info\": \"1337\"," +
                     " \"client_name\": \"Citadel Quickstart\"" +
                     "}";
          return await SendRequest("bridge-tokens/", body);
        }
    
    func getBridgeToken() (string, error) {
        productType := os.Getenv("API_PRODUCT_TYPE")
        bridgeTokenRequest := BridgeTokenRequest{ProductType: productType, ClientName: "Citadel Quickstart", TrackingInfo: "1337"}
        bridgeJson, _ := json.Marshal(bridgeTokenRequest)
        request, err := getRequest("bridge-tokens/", "POST", bridgeJson)
        if err != nil {
            return "", err
        }
        client := &http.Client{}
        response, err := client.Do(request)
    
        if err != nil {
            return "", err
        }
        data, _ := ioutil.ReadAll(response.Body)
        return (string(data)), nil
    }
    
    const getBridgeToken = async () => {
      const body = JSON.stringify({
        product_type: API_PRODUCT_TYPE,
        client_name: "Citadel Quickstart",
        tracking_info: "1337"
      })
      const responseBody = await sendRequest("bridge-tokens/", {body})
      return responseBody
    }
    
        def get_bridge_token(self) -> Any:
            """
            https://docs.citadelid.com/?python#bridge-tokens_create
            :param public_token:
            :return:
            """
            logging.info("CITADEL: Requesting bridge token from https://prod.citadelid.com/v1/bridge-tokens")
            class BridgeTokenRequest(TypedDict):
                product_type: str
                client_name: str
                tracking_info: str
    
            request_data: BridgeTokenRequest = {
                'product_type': self.PRODUCT_TYPE,
                'client_name': 'Citadel Quickstart',
                'tracking_info': '1337'
            }
    
            tokens: Any = requests.post(
                self.API_URL + 'bridge-tokens/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
            return tokens
    
      def self.getBridgeToken()
        # https://docs.citadelid.com/ruby#bridge-tokens_create
        body = { "product_type" => Citadel.product_type, "client_name" => "Citadel Quickstart", "tracking_info" => "1337" }.to_json
        return sendRequest('bridge-tokens/', body, "POST")
      end
    
        def get_bridge_token(self) -> Any:
            """
            https://docs.citadelid.com/?python#bridge-tokens_create
            :param public_token:
            :return:
            """
            logging.info("CITADEL: Requesting bridge token from https://prod.citadelid.com/v1/bridge-tokens")
            class BridgeTokenRequest(TypedDict):
                product_type: str
                client_name: str
                tracking_info: str
    
            request_data: BridgeTokenRequest = {
                'product_type': self.PRODUCT_TYPE,
                'client_name': 'Citadel Quickstart',
                'tracking_info': '1337'
            }
    
            tokens: Any = requests.post(
                self.API_URL + 'bridge-tokens/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
            return tokens
    
      [ApiController]
      [Route("getBridgeToken")]
      public class BridgeTokenController : ControllerBase
      {
    
        private Citadel _citadel = new Citadel();
    
        [HttpGet]
        public async Task<string> Get()
        {
          return await _citadel.GetBridgeToken();
        }
      }
    
    func bridgeToken(w http.ResponseWriter, r *http.Request) {
        bridgeData, err := getBridgeToken()
        if err != nil {
            fmt.Println("Error in bridgeToken\n", err)
            fmt.Fprintf(w, `{ "success": false }`)
        } else {
            fmt.Fprintf(w, bridgeData)
        }
    }
    
    app.get("/getBridgeToken", async (req, res) => {
      // retrieve bridge token
      try {
        const bridgeToken = await getBridgeToken()
        res.json(bridgeToken)
      } catch (e) {
        console.error("error with getBridgeToken")
        console.error(e)
        res.status(500).json({ success: false })
      }
    })
    
    @app.route('/getBridgeToken', methods=['GET'])
    def create_bridge_token():
        """Back end API endpoint to request a bridge token"""
        return api_client.get_bridge_token()
    
      get 'getBridgeToken', to: 'bridge_token#get'
    
    @app.route('/getBridgeToken', methods=['GET'])
    def create_bridge_token():
        """Back end API endpoint to request a bridge token"""
        return api_client.get_bridge_token()
    
  3. The application front end runs CitadelBridge.init, passing the bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. returned from the application back end and assigning callback functions.

    const bridge = CitadelBridge.init({
      bridgeToken: bridgeToken.bridge_token,
      onLoad: function() { ... },
      onSuccess: function(public_token, meta) { ... },
      onEvent: function(event_type, payload) { ... },
      onClose: function() { ... }
    });
    window.bridge = bridge;
    

User connects to payroll provider

  1. The user clicks the "Connect" button.

  2. The application front end displays BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API., which executes the onLoad callback function.

    onLoad: function () {
      console.log('loaded');
      successClosing = null
    },
    
  3. The user follows the instructions in BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API., chooses a payroll provider, logs in and clicks "Done".

  4. BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. executes the onSuccess callback function, which sends a request to the application back end with a public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration.. BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. also closes.

    onSuccess: async function (token) {
      console.log('token: ', token);
    
      successClosing = true
    
      const content = document.querySelector('.spinnerContainer');
    
      content.classList.remove('hidden');
      let verificationInfo;
      try {
        verificationInfo = await apiRequests.getVerificationInfoByToken(token);
      } catch(e) {
        console.error(e)
        content.classList.add('hidden');
        return;
      }
      content.classList.add('hidden');
    
      if (!verificationInfo.length) {
        return;
      }
    
      setUserInfo(verificationInfo[0]);
      renderEmploymentHistory(verificationInfo);
    },
    ...
    onClose: function () {
      console.log('closed');
      if (successClosing !== true) {
        renderEmploymentHistory([{ company: { address: {} } }]);
      }
    },
    

Exchange public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. and retrieve payroll data

  1. The application back end sends a request to the Citadel API, exchanging the public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. passed by BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. for an access_tokenA private token unique to a single Link. Used to fetch Link data.. While the public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. is temporary and is only used to exchange for an access_tokenA private token unique to a single Link. Used to fetch Link data., the access_tokenA private token unique to a single Link. Used to fetch Link data. is permanent and can be stored to retrieve data at a later point.

        public async Task<string> GetAccessToken(string publicToken)
        {
          Console.WriteLine("CITADEL: Exchanging a public_token for an access_token from https://prod.citadelid.com/v1/link-access-tokens");
          Console.WriteLine("CITADEL: Public Token - {0}", publicToken);
          var response = await SendRequest("link-access-tokens/", "{\"public_token\": \"" + publicToken + "\" }");
          var parsedResponse = JsonDocument.Parse(response);
          return parsedResponse.RootElement.GetProperty("access_token").GetString();
        }
    
    func getAccessToken(public_token string) (string, error) {
        fmt.Println("CITADEL: Exchanging a public_token for an access_token from https://prod.citadelid.com/v1/link-access-tokens")
        fmt.Printf("CITADEL: Public Token - %v\n", public_token)
        publicToken := PublicTokenRequest{PublicToken: public_token}
        jsonPublicToken, _ := json.Marshal(publicToken)
        accessToken := AccessTokenResponse{}
        request, err := getRequest("link-access-tokens/", "POST", jsonPublicToken)
        if err != nil {
            return "", err
        }
        client := &http.Client{}
        res, err := client.Do(request)
        defer res.Body.Close()
    
        if err != nil {
            return "", err
        }
        err = json.NewDecoder(res.Body).Decode(&accessToken)
        if err != nil {
            return "", err
        }
        return accessToken.AccessToken, nil
    }
    
    const getAccessToken = async (public_token) => {
      console.log("CITADEL: Exchanging a public_token for an access_token from https://prod.citadelid.com/v1/link-access-tokens")
      console.log(`CITADEL: Public Token - ${public_token}`)
      const body = JSON.stringify({
        public_token: public_token,
      })
      const responseBody = await sendRequest("link-access-tokens/", {body})
      return responseBody.access_token
    }
    
        def get_access_token(self, public_token: str) -> str:
            """
            https://docs.citadelid.com/?python#exchange-token-flow
            :param public_token:
            :return:
            """
            logging.info("CITADEL: Exchanging a public_token for an access_token from https://prod.citadelid.com/v1/link-access-tokens")
            logging.info("CITADEL: Public Token - %s", public_token)
            class AccessTokenRequest(TypedDict):
                public_token: str
    
            class AccessTokenResponse(TypedDict):
                access_token: str
                link_id: str
    
            request_data: AccessTokenRequest = {
                'public_token': public_token,
            }
    
            tokens: AccessTokenResponse = requests.post(
                self.API_URL + 'link-access-tokens/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
            return tokens['access_token']
    
      def self.getAccessToken(public_token)
        # https://docs.citadelid.com/?ruby#exchange-token-flow
        puts "CITADEL: Exchanging a public_token for an access_token from https://prod.citadelid.com/v1/link-access-tokens"
        puts "CITADEL: Public Token - #{public_token}"
        body = { "public_token" => public_token }.to_json
        return sendRequest('link-access-tokens/', body, "POST")["access_token"]
      end
    
        def get_access_token(self, public_token: str) -> str:
            """
            https://docs.citadelid.com/?python#exchange-token-flow
            :param public_token:
            :return:
            """
            logging.info("CITADEL: Exchanging a public_token for an access_token from https://prod.citadelid.com/v1/link-access-tokens")
            logging.info("CITADEL: Public Token - %s", public_token)
            class AccessTokenRequest(TypedDict):
                public_token: str
    
            class AccessTokenResponse(TypedDict):
                access_token: str
                link_id: str
    
            request_data: AccessTokenRequest = {
                'public_token': public_token,
            }
    
            tokens: AccessTokenResponse = requests.post(
                self.API_URL + 'link-access-tokens/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
            return tokens['access_token']
    
  2. The application back end sends a request for payroll data to the Citadel API with the access_tokenA private token unique to a single Link. Used to fetch Link data..

        public async Task<string> GetEmploymentInfoByToken(string accessToken)
        {
          return await SendRequest("verifications/employments/", "{\"access_token\": \"" + accessToken + "\" }");
        }
    
        public async Task<string> GetIncomeInfoByToken(string accessToken)
        {
          return await SendRequest("verifications/incomes/", "{\"access_token\": \"" + accessToken + "\" }");
        }
    
    func getEmploymentInfoByToken(access_token string) (string, error) {
        accessToken := AccessTokenRequest{AccessToken: access_token}
        jsonAccessToken, _ := json.Marshal(accessToken)
        request, err := getRequest("verifications/employments", "POST", jsonAccessToken)
        if err != nil {
            return "", err
        }
        client := &http.Client{}
        res, err := client.Do(request)
        defer res.Body.Close()
    
        if err != nil {
            return "", err
        }
        data, _ := ioutil.ReadAll(res.Body)
        return string(data), nil
    }
    
    // getIncomeInfoByToken uses the given access token to request
    // the associated income verification info
    func getIncomeInfoByToken(access_token string) (string, error) {
        accessToken := AccessTokenRequest{AccessToken: access_token}
        jsonAccessToken, _ := json.Marshal(accessToken)
        request, err := getRequest("verifications/incomes", "POST", jsonAccessToken)
        if err != nil {
            return "", err
        }
        client := &http.Client{}
        res, err := client.Do(request)
        defer res.Body.Close()
    
        if err != nil {
            return "", err
        }
        data, _ := ioutil.ReadAll(res.Body)
        return string(data), nil
    }
    
    const getEmploymentInfoByToken = async (access_token) => {
      const body = JSON.stringify({
        access_token,
      })
      return await sendRequest("verifications/employments/", {body})
    }
    
    /**
     * Retrieves income verifications from Citadel
     * https://docs.citadelid.com/?javascript#income-verification
     * @param {String} access_token
     * @return The response from Citadel - https://docs.citadelid.com/javascript#schemaincomecheck
     */
    const getIncomeInfoByToken = async (access_token) => {
      const body = JSON.stringify({
        access_token,
      })
      return await sendRequest("verifications/incomes/", { body })
    }
    
        def get_employment_info_by_token(self, access_token: str) -> Any:
            """
            https://docs.citadelid.com/#employment-verification
            :param access_token:
            :return:
            """
    
            class VerificationRequest(TypedDict):
                access_token: str
    
            request_data: VerificationRequest = {'access_token': access_token}
    
            return requests.post(
                self.API_URL + 'verifications/employments/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
    
        def get_income_info_by_token(self, access_token: str) -> Any:
            """
            https://docs.citadelid.com/#income-verification
            :param access_token:
            :return:
            """
    
            class VerificationRequest(TypedDict):
                access_token: str
    
            request_data: VerificationRequest = {'access_token': access_token}
    
            return requests.post(
                self.API_URL + 'verifications/incomes/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
    
      def self.getEmploymentInfoByToken(access_token)
        # https://docs.citadelid.com/?ruby#employment-verification
        body = { "access_token" => access_token }.to_json
        sendRequest('verifications/employments/', body, "POST")
      end
    
      def self.getIncomeInfoByToken(access_token)
        # https://docs.citadelid.com/?ruby#income-verification
        body = { "access_token" => access_token }.to_json
        sendRequest('verifications/incomes/', body, "POST")
      end
    
        def get_employment_info_by_token(self, access_token: str) -> Any:
            """
            https://docs.citadelid.com/#employment-verification
            :param access_token:
            :return:
            """
    
            class VerificationRequest(TypedDict):
                access_token: str
    
            request_data: VerificationRequest = {'access_token': access_token}
    
            return requests.post(
                self.API_URL + 'verifications/employments/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
    
        def get_income_info_by_token(self, access_token: str) -> Any:
            """
            https://docs.citadelid.com/#income-verification
            :param access_token:
            :return:
            """
    
            class VerificationRequest(TypedDict):
                access_token: str
    
            request_data: VerificationRequest = {'access_token': access_token}
    
            return requests.post(
                self.API_URL + 'verifications/incomes/',
                json=request_data,
                headers=self.API_HEADERS,
            ).json()
    
  3. The application back end sends the payroll data returned by the Citadel API to the application front end.

      [ApiController]
      [Route("getVerifications")]
      public class VerificationController : ControllerBase
      {
    
        private Citadel _citadel = new Citadel();
        private string _productType = Environment.GetEnvironmentVariable("API_PRODUCT_TYPE");
    
        [Route("{token}")]
        [HttpGet]
        public async Task<string> Get(string token)
        {
          var accessToken = await _citadel.GetAccessToken(token);
          if (_productType == "employment")
          {
            return await _citadel.GetEmploymentInfoByToken(accessToken);
          }
          else
          {
            return await _citadel.GetIncomeInfoByToken(accessToken);
          }
        }
      }
    
    func verifications(w http.ResponseWriter, r *http.Request) {
        productType := os.Getenv("API_PRODUCT_TYPE")
        splitPath := strings.Split(r.URL.Path, "/")
        token := splitPath[2]
        accessToken, err := getAccessToken(token)
        if err != nil {
            fmt.Println("Error getting access token", err)
            fmt.Fprintf(w, `{ "success": false }`)
            return
        }
        verificationResponse := ""
        if productType == "employment" {
            verificationResponse, err = getEmploymentInfoByToken(accessToken)
        } else {
            verificationResponse, err = getIncomeInfoByToken(accessToken)
        }
        if err != nil {
            fmt.Println("Error getting verification", err)
            fmt.Fprintf(w, `{ "success": false }`)
        } else {
            fmt.Fprintf(w, verificationResponse)
        }
    }
    
    app.get("/getVerifications/:token", async (req, res) => {
      // retrieve income verification information
      try {
        const accessToken = await getAccessToken(req.params.token)
        let verifications
        if(API_PRODUCT_TYPE === "employment") {
          verifications = await getEmploymentInfoByToken(accessToken)
        } else {
          verifications = await getIncomeInfoByToken(accessToken)
        }
        res.json(verifications)
      } catch (e) {
        console.error("error with getVerifications")
        console.error(e)
        res.status(500).json({ success: false })
      }
    })
    
    @app.route('/getVerifications/<public_token>', methods=['GET'])
    def get_verification_info_by_token(public_token: str):
        """ getVerificationInfoByToken """
    
        # First exchange public_token to access_token
        access_token = api_client.get_access_token(public_token)
    
        # Use access_token to retrieve the data
        if product_type == 'employment':
            verifications = api_client.get_employment_info_by_token(access_token)
        elif product_type == 'income':
            verifications = api_client.get_income_info_by_token(access_token)
        else:
            raise Exception('Unsupported product type!')
        return verifications
    
      get 'getVerifications/:public_token', to: 'verification#get'
    
    @app.route('/getVerifications/<public_token>', methods=['GET'])
    def get_verification_info_by_token(public_token: str):
        """ getVerificationInfoByToken """
    
        # First exchange public_token to access_token
        access_token = api_client.get_access_token(public_token)
    
        # Use access_token to retrieve the data
        if product_type == 'employment':
            verifications = api_client.get_employment_info_by_token(access_token)
        elif product_type == 'income':
            verifications = api_client.get_income_info_by_token(access_token)
        else:
            raise Exception('Unsupported product type!')
        return verifications
    
  4. The application front end renders the payroll data sent by the back end for the user to view.

    function renderPayrollData(data) {
      const historyContainer = document.querySelector("#history")
      historyContainer.innerHTML = JSON.stringify(data, null, 2)
    }
    

Explore APIs

Citadel offers a Postman collection for a no-code way to get started with the Citadel API. Follow these steps to explore the Citadel API:

  1. Install the Postman app

  2. Click the button below to install our Postman collection into your Postman app.

    Run in Postman

  3. In the top right corner of the Postman app, choose Sandbox from the environment drop down.

  4. Click the 👁 icon next to the drop down.

  5. Hover over the "Current Value" section of the "client_id" row and click the 🖍 icon that appears.

  6. Enter the Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. you copied from Get API keys into the textbox that appears and press <Enter>.

  7. Hover over the "Current Value" section of the "access_key" row and click the 🖍 icon that appears.

  8. Enter the Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. you copied from Get API keys into the textbox that appears and press <Enter>.

  9. Click the 👁 icon next to the drop down again.

Now that you have your environment configured you can begin exploring the Citadel API. Let's review how to explore and endpoint by using Postman to request a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration.:

  1. Open the "Collections" section of Postman located on the left side of the window.

  2. Expand the "Public Citadel API" item and select the "Create Bridge Token" endpoint.

  3. Open the "Documentation" section on the right of the Postman window to review the details of the endpoint.

  4. In the main section in the middle you should see a green circle next to the "Body" tab. This indicates there's data in here you can modify. Click the "Body" tab.

  5. Remove the "access_token" line from the body as this value is only used when running the Data refresh flow. Make sure to also remove the comma at the end of the "company_mapping_id" line so you'll be passing valid JSON to the endpoint.

  6. Click the blue "Send" button next to the URL. When the request is complete you can review the response returned by Citadel in the "Response" section below the "Request" section.

You can now review other endpoints. Be sure to review the Documentation for each one and the "Body" and "Params" tabs to set the values needed.

Implement Citadel

Now it's time to implement Citadel into your application. To help with your implementation we have plenty of resources available in our Help Center to answer your questions and provide tips on the best ways to implement BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. and increase conversion. If you can't find your answers there you can email devrel@citadelid.com for support.

Also be sure to review Logs and Tasks for more information on troubleshooting and debugging with the Dashboard.

Test integration

Once you've finished integrating Citadel into your development environment, it's time to test with live payroll credentials. To test with live credentials you'll need a "Development" Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request.. Head to the Dashboard onboarding page and click the "Request keys" button under the "Test your integration" section. Fill out the form to request your live keys. Once approved you will have a Development Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. available in your Dashboard. Use this Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. instead of your Sandbox key in your application to test with live credentials for up to 50 verifications.

If you don't have live payroll credentials please email us at devrel@citadelid.com for help.

Customize Bridge

Before going live you want to ensure BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. matches your applications branding as closely as possible. You can visit the Customization section of the Dashboard to customize BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API.. If you are looking for a full white labeled solution please email us at devrel@citadelid.com to get details about our enterprise offerings.

Go live

Once you've finished testing with live credentials it's time to move your Citadel integration to production. To do so you'll need a Production Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request.. Visit the Dashboard onboarding page and click the button to request production access. Fill in the form completely to give us a good idea what you will be using Citadel for. Once approved you will have a Production Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. available in your Dashboard. Use this Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. in your production environment to have full access to Citadel.

Bridge

Integrating Bridge

<html>
<head>
    <!-- Step 1 - add the Bridge library to your app with a script tag -->
    <script src="https://cdn.citadelid.com/bridge.js"></script>
</head>
<body>
<script>
  // Step 2 - Call your back end to retrieve a bridge_token from citadel
  const bridgeToken = <%= Value returned by API call to acquire bridge_token %>

  // Step 3 - Initialize Bridge
  const bridge = CitadelBridge.init({
        bridgeToken: bridgeToken.bridge_token,
        onLoad:function(){
          // Optional, called when Bridge loads
          console.log('Bridge loaded')
        },
        onSuccess:function(public_token, metadata){
          console.log('success handler')
          // Send the public_token to your server to exchange for an access_token
          // and retrieve payroll data.
          // The metadata object contains info about the Link.
          console.log("token: ",public_token)
          console.log("metadata: ", metadata)
        },
        onEvent: function(event_type, payload) {
          // all events fire this function. event_type indicates what the event is,
          // payload has additional information depending on the event.
          console.log('event: ', event_type)
          console.log('payload: ', payload)
        },
        onClose:function(){
          // Optional, called when Bridge is closed by the user.
          console.log('Bridge closed')
        }
    })
</script>
<!-- Normal page content -->
<!-- Step 4 - Create a button or action that calls bridge.open() to Bridge -->
<button type="button" id="button" onclick="bridge.open()">
  Connect
</button>
</body>
</html>

Citadel BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. is a drop-in module that provides a secure, elegant authentication. By using BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API., our customers can provide an amazing experience for employment & income verifications & other products.

Now that you have API keys and know the basics of the Citadel API, it's time to integrate with BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API., which will handle credential validation, multi-factor authentication, and error handling for each integration that we support. Integrating with BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. involves 3 steps.

  1. Use a script tag to load the bridge.js library from the Citadel servers.
  2. Make a call to a backend API endpoint to retrieve a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. from Citadel.
  3. Initialize BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. with CitadelBridge.init, passing the bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. received as a parameter.

See below for the parameters the init function accepts and the callback functions to declare.

Parameters

Property Description Required
bridgeToken The bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. value returned by the /v1/bridge-tokens API endpoint. required

Why do I need a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration.?

Citadel BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. is only meant to be used in authorized environments, so to ensure it is indeed your application requesting to initialize BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. we have you request a token from a secure setting (your back end) with your Client IDThe unique identifier passed into the X-Access-Client-Id header of every API request to authorize the request. and Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request.. By passing the resulting bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. into CitadelBridge.init we know your application is the one trying to initialize, thereby creating a secure front end environment for your users.

Sandbox credentials

There are multiple sandbox credentials available each with different scenarios to test. Below is a list of available credentials

username password scenario
<any> <any> Login Error
goodlogin goodpassword Full time current employment with annual salary
goodlogin mfa Multi factor authentication login. Use MFA Code 12345
wronglogin wrongpwd Failed importing of payroll data
bruce.banner the.hulk Part time employment, not currently employed, hourly wage
natasha.romanova black.widow Full time current employment with hourly wage
steve.rogers captain.america Partial data returned, payroll provider did not provide all data

Callbacks

Callback Description Required
onLoad A function that is called when BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. has finished loading. optional
onEvent A function that is called when an event occurs with BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API.. See events below. optional
onSuccess A function that is called when a user has successfully connected to payroll provider and closed BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API.. The function should expect two arguments, the public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. and a metadata object. required
onClose A function that is called when BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. closes. optional

onEvent types

Event Type Event Payload Description
LOAD None When BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. module has finished loading
SUCCESS [TaskData] success When the user successfully connects to their payroll provider
LINK_CREATED [TaskData] task When the user attempts to log in to their payroll provider
CLOSE [CloseData] close When BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. closes
ERROR [ErrorData] error When an error occurs during the payroll provider connection process

TaskData object

{
    "public_token": "d80ec8255dc54c5eb7cc03ac05d18ebd",
    "task_id": "2b0e7a7dec1d47678fec4e02af621cc0"
}
Name Description
public_token string A public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. that can be exchanged for an access_tokenA private token unique to a single Link. Used to fetch Link data..
task_id string A unique identifier associated with an attempt to use a LinkA connection to a payroll provider used to retrieve payroll data. to retrieve payroll data. Include this identifier when opening a support ticket for faster turnaround.

CloseData object

{
    "employer": {
        "name": "Facebook Demo"
    }
}
Name Description
public_token string A public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. associated to the LinkA connection to a payroll provider used to retrieve payroll data. where the login wasn't completed.

ErrorData object

{
    "error": {
        "error_code": "LOGIN_ERROR",
        "error_message": "Username or password is incorrect",
        "error_type": "LINK_ERROR"
    },
    "public_token": "d80ec8255dc54c5eb7cc03ac05d18ebd",
    "task_id": "5ad1938450c54024bbc967a3a7dc9020"
}
Name Description
error [Error]
public_token string The public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. associated to this connection attempt. This field will only be available if the error returned is associated to a public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration..
task_id string A unique identifier associated with an attempt to use a LinkA connection to a payroll provider used to retrieve payroll data. to retrieve payroll data. Include this identifier when opening a support ticket for faster turnaround.

Error object

Name Description
error_type string A unique code describing the type of error that occurred. See [Bridge Error Types]
error_code string A unique code indicating the error that occurred. See [Bridge Error Codes]
error_message string A description of the error that occurred

Bridge token

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/bridge-tokens/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/bridge-tokens/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/bridge-tokens/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "tracking_info": "any data for tracking current user",
  "client_name": "Citadel Demo",
  "product_type": "employment",
  "company_mapping_id": "99dd17074ac94aa9ace2621d657c7610",
  "access_token": "99dd17074ac94aa9ace2621d657c7610",
  "account": {
    "account_number": "16002600",
    "account_type": "checking",
    "routing_number": "123456789",
    "bank_name": "TD Bank"
  }
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/bridge-tokens/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/bridge-tokens/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/bridge-tokens/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

The following endpoint returns a bridge token. This is typically the first endpoint called before initializing the Bridge since the response from this call is passed to the CitadelBridge.init function.

HTTP Request

POST /bridge-tokens/

Body parameter

{
  "tracking_info": "any data for tracking current user",
  "client_name": "Citadel Demo",
  "product_type": "employment",
  "company_mapping_id": "99dd17074ac94aa9ace2621d657c7610",
  "access_token": "99dd17074ac94aa9ace2621d657c7610",
  "account": {
    "account_number": "16002600",
    "account_type": "checking",
    "routing_number": "123456789",
    "bank_name": "TD Bank"
  }
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
tracking_info body string (optional) Any information to associate with current user.
client_name body string (optional) Client name to be used in the Bridge.
product_type body string Indicates type of product to use: employment, income, admin, deposit_switch or fas.
company_mapping_id body string (optional) A mapping ID from a company to payroll provider.
access_token body string (optional) Access token of the existing link (used for the data refresh).
account body object (optional) Bank account info. Used for Direct deposit and Funding account switching
» account_number body string Account number
» account_type body string Account type
» routing_number body string Routing number
» bank_name body string Bank name

Enumerated Values

Parameter Value
product_type employment
product_type income
product_type admin
product_type deposit_switch
product_type fas
» account_type checking
» account_type savings

Example responses

201 Response

{
  "bridge_token": "2f67984a110747d190c39e1022c81837",
  "tracking_info": "any data for tracking current user",
  "client_name": "Citadel Demo",
  "product_type": "employment",
  "company_mapping_id": "99dd17074ac94aa9ace2621d657c7610",
  "access_token": "99dd17074ac94aa9ace2621d657c7610"
}

Responses

Status Object
201 BridgeTokenCreateResponse

BridgeTokenCreateResponse object

{
  "bridge_token": "2f67984a110747d190c39e1022c81837",
  "tracking_info": "any data for tracking current user",
  "client_name": "Citadel Demo",
  "product_type": "employment",
  "company_mapping_id": "99dd17074ac94aa9ace2621d657c7610",
  "access_token": "99dd17074ac94aa9ace2621d657c7610"
}

Data for the bridge token

Name Description
bridge_token string The bridge token
tracking_info string (nullable) Any information to associate with current user.
client_name string (nullable) Client name to be used in the Bridge.
product_type string (nullable) Indicates type of product to use: employment, income or admin.
company_mapping_id string (nullable) A mapping ID from a company to payroll provider.
access_token string (nullable) Access token of the existing link (used for the data refresh).

Exchange token flow

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/link-access-tokens/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/link-access-tokens/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/link-access-tokens/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "public_token": "48427a36d43c4d5aa6324bc06c692456"
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/link-access-tokens/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/link-access-tokens/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/link-access-tokens/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

Exchange a Bridge public_token for an API access_token. Bridge hands off the public_token client-side via the onSuccess callback once a user has successfully created a Link. The public_token is ephemeral and expires after 6 hours. A public_token becomes invalidated once it has been successfully exchanged for an access_token.

HTTP Request

POST /link-access-tokens/

Body parameter

{
  "public_token": "48427a36d43c4d5aa6324bc06c692456"
}
Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
public_token body string Public token for exchange

Example responses

201 Response

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "link_id": "24d7e80942ce4ad58a93f70ce4115f5c"
}

Status Code 201

Name Description
» access_token string Access token
» link_id string Link ID

Deeplinking

If you already have data about the user beforehand you can leverage deeplinking to skip unnecessary screens in BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. by providing certain fields when requesting a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration.. Here's the available options you have:

Skip employer selection (company_mapping_id)

If you know the users employer ahead of time you can leverage the Company search endpoint to retrieve a company_mapping_id. By passing this value into the company_mapping_id field in your /bridge_tokens request BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. will bypass the employer selection screen and the user will begin by selecting their payroll provider.

Skip payroll provider selection (provider_id)

If you know the payroll provider you want the user to log into you can have the user start directly on the login screen by passing the provider_id field in your /bridge-tokens request. Any supported provider provided by the Support providers endpoint will be valid to put in this field.

Data processing

We make every effort to make data available as soon as possible, however you should note that sometimes the processing of documents (like pdf files) from payroll providers takes time. As a result, the endpoints to retrieve employment verification, income verification and payroll admin will return as much data as possible at all times but it's important to subscribe to Webhooks so you can track the status of each TaskA process where a Link is used to pull data from a payroll provider. and pull all available data when the status is done.

Storing API identifiers

It’s important to properly handle identifiers returned by BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. and direct API endpoints.

Citadel's identifiers let you associate API and Provider events with your requests, and will help our support team resolve your issues in a more expeditous and accurate manner.

access_tokenA private token unique to a single Link. Used to fetch Link data.s are the core identifiers that map your end-users to LinkA connection to a payroll provider used to retrieve payroll data.s.

You should store these securely and associate them with users of your application.

Make sure, however, that these identifiers are never exposed on the client-side. Bear in mind that one user can create multiple access_tokenA private token unique to a single Link. Used to fetch Link data.s if they have accounts with multiple providers.

Companies and providers

Company list

Code samples

import requests
headers = {
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.get('/v1/companies/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }

    /// Make a dummy request
    public async Task MakeGetRequest()
    {
      string url = "/v1/companies/";
      var result = await GetAsync(url);
    }

    /// Performs a GET Request
    public async Task GetAsync(string url)
    {
        //Start the request
        HttpResponseMessage response = await Client.GetAsync(url);

        //Validate result
        response.EnsureSuccessStatusCode();

    }




    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/v1/companies/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');

const headers = {
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/companies/',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.get '/v1/companies/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X GET /v1/companies/ \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint returns list of company mappings.

HTTP Request

GET /companies/

Request Fields

Name In Description
page query string (optional) Page number
page_size query integer (optional) Number of results to return per page.
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key

Example responses

200 Response

{
  "count": "100",
  "next": "https://prod.citadelid.com/v1/companies?page=1",
  "previous": "https://prod.citadelid.com/v1/companies?page=3",
  "results": [
    {
      "id": "48427a36d43c4d5aa6324bc06c692456",
      "name": "Facebook Demo",
      "domain": "https://www.facebook.com"
    }
  ]
}

Responses

Status Object
200 CompanyMappingList

CompanyMappingList object

{
  "count": "100",
  "next": "https://prod.citadelid.com/v1/companies?page=1",
  "previous": "https://prod.citadelid.com/v1/companies?page=3",
  "results": [
    {
      "id": "48427a36d43c4d5aa6324bc06c692456",
      "name": "Facebook Demo",
      "domain": "https://www.facebook.com"
    }
  ]
}

Company Mappings List

Name Description
count integer Number of results on page
next string Link to next page
previous string Link to previous page
results [CompanyMappingListItem] List of company mappings

CompanyMappingListItem object

{
  "id": "48427a36d43c4d5aa6324bc06c692456",
  "name": "Facebook Demo",
  "domain": "https://www.facebook.com"
}

CompanyMappingListItem

Name Description
id string (nullable) Company to Payroll Provider mapping ID
name string Company name
domain string (nullable) Company domain

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/companies/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/companies/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/companies/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "name": "Google",
  "address": {
    "street": "1600 Amphitheatre Pkwy",
    "city": "Mountain View",
    "state": "CA",
    "zip": "94043"
  }
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/companies/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/companies/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/companies/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint returns a payroll provider company_mapping_id in case it exists. This company_mapping_id can be passed to the /bridge-tokens endpoint to have the user skip the company selection step and suggest a payroll provider.

HTTP Request

POST /companies/

Body parameter

{
  "name": "Google",
  "address": {
    "street": "1600 Amphitheatre Pkwy",
    "city": "Mountain View",
    "state": "CA",
    "zip": "94043"
  }
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
name body string Company Name
address body object (optional) Company location address object
» street body string Company location street
» city body string Company location city
» state body string (optional) Company location state
» zip body string Company location zip code

Example responses

201 Response

{
  "company_mapping_id": "48427a36d43c4d5aa6324bc06c692456"
}

Response Fields

Status Code 201

Name Description
» company_mapping_id string (nullable) Company to Payroll Provider mapping ID

Sandbox values

In our sandbox environment there is a limited number of company names that will return a company_mapping_id. You can choose any one of these values when developing your Citadel integration to get a valid company_mapping_id.

Name
Facebook
Bank of America
Kroger
UPS
Fannie Mae
Freddie Mac

Supported providers

Code samples

import requests
headers = {
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.get('/v1/payroll-providers/supported/{product_type}/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }

    /// Make a dummy request
    public async Task MakeGetRequest()
    {
      string url = "/v1/payroll-providers/supported/{product_type}/";
      var result = await GetAsync(url);
    }

    /// Performs a GET Request
    public async Task GetAsync(string url)
    {
        //Start the request
        HttpResponseMessage response = await Client.GetAsync(url);

        //Validate result
        response.EnsureSuccessStatusCode();

    }




    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/v1/payroll-providers/supported/{product_type}/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');

const headers = {
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/payroll-providers/supported/{product_type}/',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.get '/v1/payroll-providers/supported/{product_type}/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X GET /v1/payroll-providers/supported/{product_type}/ \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

The following endpoint returns supported payroll providers filtered by product_type.

HTTP Request

GET /payroll-providers/supported/{product_type}/

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
product_type path string Product type

Enumerated Values

Parameter Value
product_type employment
product_type income
product_type deposit_switch
product_type fas
product_type admin

Example responses

200 Response

{
  "id": "adp",
  "is_supported": true
}

Responses

Status Object
200 ProviderSupportedByProductType

ProviderSupportedByProductType object

{
  "id": "adp",
  "is_supported": true
}

Name Description
id string ID of provider
is_supported string Is product_type supported for this provider

Verification

Employment history

Citadel enables applicants to verify employment instantly through their payroll provider.

Our employment verification product can help CRAs and staffing agencies provide an exceptional user experience and almost instantly get a complete picture of applicant's employment history.

Note: Please check the Data Processing section for information on how Citadel processes verifications and the importance of monitoring the status field of this endpoint.

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/verifications/employments/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/verifications/employments/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/verifications/employments/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/verifications/employments/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/verifications/employments/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/verifications/employments/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

The following endpoint returns the Employment data:

HTTP Request

POST /verifications/employments/

Body parameter

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
access_token body string Access Token

Example responses

200 Response

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "provider": "adp",
  "tracking_info": "user123456",
  "employments": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "job_title": "PR associate",
      "job_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "external_last_updated": "2019-08-24",
      "original_hire_date": "2017-06-21",
      "is_active": false,
      "dates_from_statements": false,
      "profile": {
        "first_name": "John",
        "last_name": "Doe",
        "middle_initials": "K",
        "ssn": "123456789",
        "email": "john.doe@example.com",
        "date_of_birth": "1992-03-03",
        "home_address": {
          "street": "1 Morgan Ave",
          "city": "Los Angeles",
          "state": "CA",
          "zip": "90210"
        }
      },
      "company": {
        "name": "Facebook Demo",
        "address": {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025"
        },
        "phone": "6503087300"
      }
    }
  ]
}

Responses

Status Object
200 EmploymentCheck

EmploymentCheck object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "provider": "adp",
  "tracking_info": "user123456",
  "employments": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "job_title": "PR associate",
      "job_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "external_last_updated": "2019-08-24",
      "original_hire_date": "2017-06-21",
      "is_active": false,
      "dates_from_statements": false,
      "profile": {
        "first_name": "John",
        "last_name": "Doe",
        "middle_initials": "K",
        "ssn": "123456789",
        "email": "john.doe@example.com",
        "date_of_birth": "1992-03-03",
        "home_address": {
          "street": "1 Morgan Ave",
          "city": "Los Angeles",
          "state": "CA",
          "zip": "90210"
        }
      },
      "company": {
        "name": "Facebook Demo",
        "address": {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025"
        },
        "phone": "6503087300"
      }
    }
  ]
}

Data for the employment verification

Name Description
id string Unique ID
status string Request status
access_token string Access token for a Link to payroll provider
provider string Payroll provider name (Deprecated)
tracking_info string Any information passed to the Citadel Bridge from a partner
employments [Employment] List of employments received from a payroll provider

Employment object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "job_title": "PR associate",
  "job_type": "F",
  "start_date": "2018-01-01",
  "end_date": "2019-08-24",
  "external_last_updated": "2019-08-24",
  "original_hire_date": "2017-06-21",
  "is_active": false,
  "dates_from_statements": false,
  "profile": {
    "first_name": "John",
    "last_name": "Doe",
    "middle_initials": "K",
    "ssn": "123456789",
    "email": "john.doe@example.com",
    "date_of_birth": "1992-03-03",
    "home_address": {
      "street": "1 Morgan Ave",
      "city": "Los Angeles",
      "state": "CA",
      "zip": "90210"
    }
  },
  "company": {
    "name": "Facebook Demo",
    "address": {
      "street": "1 Hacker Way",
      "city": "Menlo Park",
      "state": "CA",
      "zip": "94025"
    },
    "phone": "6503087300"
  }
}

Employment data

Name Description
id string Unique ID
job_title string (nullable) Employee's job title
job_type string (nullable) Employee's job type
start_date string(date) Employee's hire date
end_date string(date) (nullable) Employee's termination date
external_last_updated string(date) (nullable) Indicates the date when employment data was last updated on the Payroll Provider side
original_hire_date string(date) (nullable) Original hire date
is_active boolean (nullable) Indicates whether the employment is still active
dates_from_statements boolean (nullable) Indicates whether or not the Employee's hire and/or termination dates were derived from first/last pay statements
profile Profile Person's identity information
company Company Company information

Enumerated Values

Property Value Description
job_type F Full Time
job_type P Part Time
job_type S Seasonal
job_type D Daily (per diem)
job_type C Contract

Income and employment

Citadel enables users to verify income instantly through their payroll provider, giving you a complete picture of their income.

Income verification can help lenders and rental companies streamline the application process.

Note: Please check the Data Processing section for information on how Citadel processes verifications and the importance of monitoring the status field of this endpoint.

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/verifications/incomes/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/verifications/incomes/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/verifications/incomes/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/verifications/incomes/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/verifications/incomes/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/verifications/incomes/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

The following endpoint returns the Income data:

HTTP Request

POST /verifications/incomes/

Body parameter

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
access_token body string Access Token

Example responses

200 Response

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "refresh_status": "new",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "provider": "adp",
  "tracking_info": "user123456",
  "employments": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "job_title": "PR associate",
      "job_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "external_last_updated": "2019-08-24",
      "original_hire_date": "2017-06-21",
      "is_active": false,
      "dates_from_statements": false,
      "income": "70000.00",
      "income_unit": "YEARLY",
      "pay_frequency": "M",
      "manager_name": "Jenny McDouglas",
      "profile": {
        "first_name": "John",
        "last_name": "Doe",
        "middle_initials": "K",
        "ssn": "123456789",
        "email": "john.doe@example.com",
        "date_of_birth": "1992-03-03",
        "home_address": {
          "street": "1 Morgan Ave",
          "city": "Los Angeles",
          "state": "CA",
          "zip": "90210"
        }
      },
      "company": {
        "name": "Facebook Demo",
        "address": {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025"
        },
        "phone": "6503087300"
      },
      "statements": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "pay_date": "2018-05-15",
          "net_pay": "11500.32",
          "net_pay_ytd": "31980.64",
          "gross_pay": "13900.11",
          "gross_pay_ytd": "49200.00",
          "bonus": "100.00",
          "commission": "12000.00",
          "hours": "40.00",
          "basis_of_pay": "S",
          "period_start": "2018-05-01",
          "period_end": "2018-05-15",
          "regular": "1695.11",
          "regular_ytd": "23000.00",
          "bonus_ytd": "1000.00",
          "commission_ytd": "24000.00",
          "overtime": "45.00",
          "overtime_ytd": "500.00",
          "other_pay": "60.00",
          "other_pay_ytd": "700.00",
          "earnings": {},
          "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
          "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
        }
      ],
      "annual_income_summary": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "year": 2018,
          "regular": "23000.00",
          "bonus": "1000.00",
          "commission": "24000.00",
          "overtime": "500.00",
          "other_pay": "700.00",
          "net_pay": "31980.64",
          "gross_pay": "49200.00"
        }
      ],
      "bank_accounts": [
        {
          "account_number": "1234567890",
          "routing_number": "123456789",
          "account_name": "My Bank",
          "account_type": "C",
          "deposit_type": "A",
          "deposit_value": "200.00",
          "bank_name": "TD Bank"
        }
      ],
      "annual_salary": "70000.00",
      "hourly_salary": "36.40",
      "w2s": [
        {
          "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/W2_sample.pdf",
          "year": 2020
        }
      ]
    }
  ]
}

Responses

Status Object
200 IncomeCheck

IncomeCheck object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "refresh_status": "new",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "provider": "adp",
  "tracking_info": "user123456",
  "employments": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "job_title": "PR associate",
      "job_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "external_last_updated": "2019-08-24",
      "original_hire_date": "2017-06-21",
      "is_active": false,
      "dates_from_statements": false,
      "income": "70000.00",
      "income_unit": "YEARLY",
      "pay_frequency": "M",
      "manager_name": "Jenny McDouglas",
      "profile": {
        "first_name": "John",
        "last_name": "Doe",
        "middle_initials": "K",
        "ssn": "123456789",
        "email": "john.doe@example.com",
        "date_of_birth": "1992-03-03",
        "home_address": {
          "street": "1 Morgan Ave",
          "city": "Los Angeles",
          "state": "CA",
          "zip": "90210"
        }
      },
      "company": {
        "name": "Facebook Demo",
        "address": {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025"
        },
        "phone": "6503087300"
      },
      "statements": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "pay_date": "2018-05-15",
          "net_pay": "11500.32",
          "net_pay_ytd": "31980.64",
          "gross_pay": "13900.11",
          "gross_pay_ytd": "49200.00",
          "bonus": "100.00",
          "commission": "12000.00",
          "hours": "40.00",
          "basis_of_pay": "S",
          "period_start": "2018-05-01",
          "period_end": "2018-05-15",
          "regular": "1695.11",
          "regular_ytd": "23000.00",
          "bonus_ytd": "1000.00",
          "commission_ytd": "24000.00",
          "overtime": "45.00",
          "overtime_ytd": "500.00",
          "other_pay": "60.00",
          "other_pay_ytd": "700.00",
          "earnings": {},
          "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
          "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
        }
      ],
      "annual_income_summary": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "year": 2018,
          "regular": "23000.00",
          "bonus": "1000.00",
          "commission": "24000.00",
          "overtime": "500.00",
          "other_pay": "700.00",
          "net_pay": "31980.64",
          "gross_pay": "49200.00"
        }
      ],
      "bank_accounts": [
        {
          "account_number": "1234567890",
          "routing_number": "123456789",
          "account_name": "My Bank",
          "account_type": "C",
          "deposit_type": "A",
          "deposit_value": "200.00",
          "bank_name": "TD Bank"
        }
      ],
      "annual_salary": "70000.00",
      "hourly_salary": "36.40",
      "w2s": [
        {
          "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/W2_sample.pdf",
          "year": 2020
        }
      ]
    }
  ]
}

Data for the income verification

Name Description
id string Unique ID
status string Request status
refresh_status string (nullable) Status of most recent refresh task
access_token string Access token for a Link to payroll provider
provider string Payroll provider name (Deprecated)
tracking_info string Any information passed to the Citadel Bridge from a partner
employments [EmploymentIncome] List of employments received from a payroll provider

EmploymentIncome object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "job_title": "PR associate",
  "job_type": "F",
  "start_date": "2018-01-01",
  "end_date": "2019-08-24",
  "external_last_updated": "2019-08-24",
  "original_hire_date": "2017-06-21",
  "is_active": false,
  "dates_from_statements": false,
  "income": "70000.00",
  "income_unit": "YEARLY",
  "pay_frequency": "M",
  "manager_name": "Jenny McDouglas",
  "profile": {
    "first_name": "John",
    "last_name": "Doe",
    "middle_initials": "K",
    "ssn": "123456789",
    "email": "john.doe@example.com",
    "date_of_birth": "1992-03-03",
    "home_address": {
      "street": "1 Morgan Ave",
      "city": "Los Angeles",
      "state": "CA",
      "zip": "90210"
    }
  },
  "company": {
    "name": "Facebook Demo",
    "address": {
      "street": "1 Hacker Way",
      "city": "Menlo Park",
      "state": "CA",
      "zip": "94025"
    },
    "phone": "6503087300"
  },
  "statements": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "pay_date": "2018-05-15",
      "net_pay": "11500.32",
      "net_pay_ytd": "31980.64",
      "gross_pay": "13900.11",
      "gross_pay_ytd": "49200.00",
      "bonus": "100.00",
      "commission": "12000.00",
      "hours": "40.00",
      "basis_of_pay": "S",
      "period_start": "2018-05-01",
      "period_end": "2018-05-15",
      "regular": "1695.11",
      "regular_ytd": "23000.00",
      "bonus_ytd": "1000.00",
      "commission_ytd": "24000.00",
      "overtime": "45.00",
      "overtime_ytd": "500.00",
      "other_pay": "60.00",
      "other_pay_ytd": "700.00",
      "earnings": {},
      "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
      "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
    }
  ],
  "annual_income_summary": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "year": 2018,
      "regular": "23000.00",
      "bonus": "1000.00",
      "commission": "24000.00",
      "overtime": "500.00",
      "other_pay": "700.00",
      "net_pay": "31980.64",
      "gross_pay": "49200.00"
    }
  ],
  "bank_accounts": [
    {
      "account_number": "1234567890",
      "routing_number": "123456789",
      "account_name": "My Bank",
      "account_type": "C",
      "deposit_type": "A",
      "deposit_value": "200.00",
      "bank_name": "TD Bank"
    }
  ],
  "annual_salary": "70000.00",
  "hourly_salary": "36.40",
  "w2s": [
    {
      "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/W2_sample.pdf",
      "year": 2020
    }
  ]
}

Income data

Name Description
id string Unique ID
job_title string (nullable) Employee's job title
job_type string (nullable) Employee's job type
start_date string(date) (nullable) Employee's hire date
end_date string(date) (nullable) Employee's termination date
external_last_updated string(date) (nullable) Indicates the date when employment data was last updated on the Payroll Provider side
original_hire_date string(date) (nullable) Original hire date
is_active boolean (nullable) Indicates whether the employment is still active
dates_from_statements boolean (nullable) Indicates whether or not the Employee's hire and/or termination dates were derived from first/last pay statements
income string(decimal) (nullable) Income amount not including commission or bonuses
income_unit string (nullable) The pay interval the income field refers to
pay_frequency string (nullable) Pay frequency
manager_name string (nullable) Supervisor's name
profile Profile Person's identity information
company Company Company information
statements [Statement] (nullable) List of paystubs received from a payroll provider
annual_income_summary [AnnualIncomeSummary] (nullable) Annual income summary by years
bank_accounts [BankAccount] (nullable) List of bank accounts linked to the employment
annual_salary string(decimal) (nullable) Annual income (Deprecated)
hourly_salary string(decimal) (nullable) Hourly income (Deprecated)
w2s [W2] (nullable) List of W-2 forms linked to the employment

Enumerated Values

Property Value Description
job_type F Full Time
job_type P Part Time
job_type S Seasonal
job_type D Daily (per diem)
job_type C Contract
income_unit YEARLY Yearly
income_unit MONTHLY Monthly
income_unit WEEKLY Weekly
income_unit DAILY Daily
income_unit HOURLY Hourly
pay_frequency M Monthly
pay_frequency SM Semi-Monthly
pay_frequency W Weekly
pay_frequency BW Bi-Weekly

W2 object

{
  "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/W2_sample.pdf",
  "year": 2020
}

W-2 Form

Name Description
file string(uri) Link to a W2 report file (format is specified in the content-type)
year integer Year

AnnualIncomeSummary object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "year": 2018,
  "regular": "23000.00",
  "bonus": "1000.00",
  "commission": "24000.00",
  "overtime": "500.00",
  "other_pay": "700.00",
  "net_pay": "31980.64",
  "gross_pay": "49200.00"
}

Annual income summary

Name Description
id string Unique ID
year integer Income report year
regular string(decimal) (nullable) Regular salary
bonus string(decimal) (nullable) Bonus
commission string(decimal) (nullable) Commission
overtime string(decimal) (nullable) Overtime pay
other_pay string(decimal) (nullable) All other pays
net_pay string(decimal) (nullable) Net pay
gross_pay string(decimal) (nullable) Gross pay

Admin connect

Citadel enables companies to share their payroll data including a directory of employees and payroll history.

Payroll admin can help companies to better run FP&A analyses, calculate taxes or onboard employees via a directory.

Note: Please check the Data Processing section for information on how Citadel processes verifications and the importance of monitoring the status field of these endpoints.

Employee directory

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/administrators/directories/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/administrators/directories/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/administrators/directories/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/administrators/directories/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/administrators/directories/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/administrators/directories/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint returns the Employee Directory.

HTTP Request

POST /administrators/directories/

Body parameter

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
page query string (optional) Page number to paginate "directory" content.
access_token body string Access Token

Example responses

200 Response

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "refresh_status": "new",
  "report_date": "2020-03-10",
  "directory": [
    {
      "employee_id": "sg235pl",
      "employee_number": "123",
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "dob": "1992-03-03",
      "gender": "F",
      "email_personal": "john.doe@example.com",
      "email_work": "work@example.com",
      "phone_number": "6501111111",
      "home_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "country": "US",
        "zip": "94025"
      },
      "position_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "title": "CEO",
      "department": "Engineering",
      "work_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "country": "US",
        "zip": "94025"
      },
      "manager_id": "5ca65d8a92dabb7c16d8fd55",
      "manager_first_name": "John",
      "manager_last_name": "Smith",
      "income": "150000.00",
      "income_currency": "USD",
      "income_unit": "YEARLY",
      "pay_frequency": "M",
      "is_active": true
    }
  ],
  "count": 0,
  "page_size": 100,
  "next": "https://prod.citadelid.com/v1/administrators/directories/?page=2",
  "previous": "https://prod.citadelid.com/v1/administrators/directories/"
}

Responses

Status Object
200 DirectoryResponse

DirectoryResponse object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "refresh_status": "new",
  "report_date": "2020-03-10",
  "directory": [
    {
      "employee_id": "sg235pl",
      "employee_number": "123",
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "dob": "1992-03-03",
      "gender": "F",
      "email_personal": "john.doe@example.com",
      "email_work": "work@example.com",
      "phone_number": "6501111111",
      "home_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "country": "US",
        "zip": "94025"
      },
      "position_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "title": "CEO",
      "department": "Engineering",
      "work_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "country": "US",
        "zip": "94025"
      },
      "manager_id": "5ca65d8a92dabb7c16d8fd55",
      "manager_first_name": "John",
      "manager_last_name": "Smith",
      "income": "150000.00",
      "income_currency": "USD",
      "income_unit": "YEARLY",
      "pay_frequency": "M",
      "is_active": true
    }
  ],
  "count": 0,
  "page_size": 100,
  "next": "https://prod.citadelid.com/v1/administrators/directories/?page=2",
  "previous": "https://prod.citadelid.com/v1/administrators/directories/"
}

Employee Directory report

Name Description
id string Unique ID
status string Request status
refresh_status string Status of most recent refresh task
report_date string(date) Date of report creation
directory [Directory] List of directory records
count integer Total count of objects in "directory"
page_size integer Size of one page of "directory" objects
next string(uri) Request URL for the next page (optional)
previous string(uri) Request URL for the previous page (optional)

Directory object

{
  "employee_id": "sg235pl",
  "employee_number": "123",
  "first_name": "John",
  "last_name": "Doe",
  "full_name": "John Doe",
  "dob": "1992-03-03",
  "gender": "F",
  "email_personal": "john.doe@example.com",
  "email_work": "work@example.com",
  "phone_number": "6501111111",
  "home_address": {
    "street": "1 Hacker Way",
    "city": "Menlo Park",
    "state": "CA",
    "country": "US",
    "zip": "94025"
  },
  "position_type": "F",
  "start_date": "2018-01-01",
  "end_date": "2019-08-24",
  "title": "CEO",
  "department": "Engineering",
  "work_address": {
    "street": "1 Hacker Way",
    "city": "Menlo Park",
    "state": "CA",
    "country": "US",
    "zip": "94025"
  },
  "manager_id": "5ca65d8a92dabb7c16d8fd55",
  "manager_first_name": "John",
  "manager_last_name": "Smith",
  "income": "150000.00",
  "income_currency": "USD",
  "income_unit": "YEARLY",
  "pay_frequency": "M",
  "is_active": true
}

This object represents one item in the Employee Directory

Name Description
employee_id string (nullable) Employee ID from the payroll system
employee_number string (nullable) Employee number from the payroll system
first_name string First name
last_name string Last name
full_name string (nullable) Full name
dob string(date) (nullable) Date of birth
gender string (nullable) Gender
email_personal string(email) (nullable) Personal email address
email_work string(email) (nullable) Work email address
phone_number string (nullable) Phone number
home_address object (nullable) Home address
» street string Home location street
» city string Home location city
» state string Home location state
» country string (nullable) Home location country
» zip string Home location zip code
position_type string (nullable) Employee's job type
start_date string(date) (nullable) Employee's hire date
end_date string(date) (nullable) Employee's termination date
title string (nullable) Job title
department string (nullable) Department
work_address object (nullable) Company address
» street string Company location street
» city string Company location city
» state string Company location state
» country string (nullable) Home location country
» zip string Company location zip code
manager_id string (nullable) Manager's ID
manager_first_name string (nullable) Manager's first name
manager_last_name string (nullable) Manager's last name
income number (nullable) Income amount not including commission or bonuses
income_currency string (nullable) Income currency (ISO 4217 format)
income_unit string (nullable) The pay interval the income field refers to
pay_frequency string (nullable) Pay frequency
is_active boolean (nullable) Employment status

Enumerated Values

Property Value Description
gender F Female
gender M Male
position_type F Full Time
position_type P Part Time
position_type S Seasonal
position_type D Daily (per diem)
position_type C Contract
income_unit YEARLY Yearly
income_unit MONTHLY Monthly
income_unit WEEKLY Weekly
income_unit DAILY Daily
income_unit HOURLY Hourly
pay_frequency M Monthly
pay_frequency SM Semi-Monthly
pay_frequency W Weekly
pay_frequency BW Bi-Weekly

Payroll history

Create payroll report

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/administrators/payrolls/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/administrators/payrolls/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/administrators/payrolls/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "start_date": "2020-03-09",
  "end_date": "2020-03-10"
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/administrators/payrolls/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/administrators/payrolls/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/administrators/payrolls/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint creates a payroll admin report request.

HTTP Request

POST /administrators/payrolls/

Body parameter

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "start_date": "2020-03-09",
  "end_date": "2020-03-10"
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
access_token body string Access token for a Link to payroll provider
start_date body string(date) Start date of report
end_date body string(date) End date of report

Example responses

201 Response

{
  "payroll_report_id": "48427a36d43c4d5aa6324bc06c692456"
}

Response Fields

Status Code 201

Name Description
» payroll_report_id string Unique ID for the payroll report request

Retrieve payroll report

Code samples

import requests
headers = {
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.get('/v1/administrators/payrolls/{payroll_report_id}/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }

    /// Make a dummy request
    public async Task MakeGetRequest()
    {
      string url = "/v1/administrators/payrolls/{payroll_report_id}/";
      var result = await GetAsync(url);
    }

    /// Performs a GET Request
    public async Task GetAsync(string url)
    {
        //Start the request
        HttpResponseMessage response = await Client.GetAsync(url);

        //Validate result
        response.EnsureSuccessStatusCode();

    }




    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/v1/administrators/payrolls/{payroll_report_id}/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');

const headers = {
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/administrators/payrolls/{payroll_report_id}/',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.get '/v1/administrators/payrolls/{payroll_report_id}/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X GET /v1/administrators/payrolls/{payroll_report_id}/ \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint returns a payroll admin report.

HTTP Request

GET /administrators/payrolls/{payroll_report_id}/

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
payroll_report_id path string Report ID received from a /administrators/payrolls request
page query string (optional) Page number to paginate "payroll_report" content.

Example responses

201 Response

{
  "id": "24d7e80942ce4ad58a93f70z",
  "payroll_report": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "employee_id": "sg235pl",
      "employee_number": "123",
      "first_name": "John",
      "last_name": "Doe",
      "net_pay": "4382.68",
      "gross_pay": "6250.00",
      "employee_taxes": "1800.31",
      "employer_taxes": "473.00",
      "period_start": "2020-03-01",
      "period_end": "2020-03-14",
      "pay_date": "2020-03-15"
    }
  ],
  "report_date": "2020-03-10",
  "start_date": "2020-03-09",
  "end_date": "2020-03-10",
  "status": "new",
  "count": 0,
  "page_size": 100,
  "next": "https://prod.citadelid.com/v1/administrators/payrolls/24d7e80942ce4ad58a93f70z?page=2",
  "previous": "https://prod.citadelid.com/v1/administrators/payrolls/24d7e80942ce4ad58a93f70z"
}

Responses

Status Object
201 PayrollReport

PayrollReport object

{
  "id": "24d7e80942ce4ad58a93f70z",
  "payroll_report": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "employee_id": "sg235pl",
      "employee_number": "123",
      "first_name": "John",
      "last_name": "Doe",
      "net_pay": "4382.68",
      "gross_pay": "6250.00",
      "employee_taxes": "1800.31",
      "employer_taxes": "473.00",
      "period_start": "2020-03-01",
      "period_end": "2020-03-14",
      "pay_date": "2020-03-15"
    }
  ],
  "report_date": "2020-03-10",
  "start_date": "2020-03-09",
  "end_date": "2020-03-10",
  "status": "new",
  "count": 0,
  "page_size": 100,
  "next": "https://prod.citadelid.com/v1/administrators/payrolls/24d7e80942ce4ad58a93f70z?page=2",
  "previous": "https://prod.citadelid.com/v1/administrators/payrolls/24d7e80942ce4ad58a93f70z"
}

Payroll report

Name Description
id string Unique report ID
payroll_report [PayrollItem] List of payroll records
report_date string(date) Date of report creation
start_date string(date) Start date of report
end_date string(date) End date of report
status string Report status
count integer Total count of objects in "payroll_report"
page_size integer Size of one page of "payroll_report" objects
next string(uri) Request URL for the next page (optional)
previous string(uri) Request URL for the previous page (optional)

PayrollItem object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "employee_id": "sg235pl",
  "employee_number": "123",
  "first_name": "John",
  "last_name": "Doe",
  "net_pay": "4382.68",
  "gross_pay": "6250.00",
  "employee_taxes": "1800.31",
  "employer_taxes": "473.00",
  "period_start": "2020-03-01",
  "period_end": "2020-03-14",
  "pay_date": "2020-03-15"
}

Payroll report item.

Name Description
id string Unique ID
employee_id string (nullable) Employee ID from the payroll system
employee_number string (nullable) Employee number from the payroll system
first_name string First name
last_name string Last name
net_pay string(decimal) Net pay
gross_pay string(decimal) Gross pay
employee_taxes string(decimal) Employee taxes
employer_taxes string(decimal) Employer taxes
period_start string(date) (nullable) Start date of the pay period
period_end string(date) (nullable) End date of the pay period
pay_date string(date) (nullable) Pay date of the period

Switching

Direct deposit

Creating a direct deposit switch request is done when creating a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration.. The Bridge token endpoint accepts deposit_switch as a value for product_type. When specifying a deposit_switch value you must also provide the following fields: account.account_number, account.account_type, account.routing_number, account.bank_name. Currently you can only request a full direct deposit switch.

Report

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/deposit-switches/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/deposit-switches/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/deposit-switches/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/deposit-switches/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/deposit-switches/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/deposit-switches/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint returns the DDS report.

HTTP Request

POST /deposit-switches/

Body parameter

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
access_token body string Access Token

Example responses

200 Response

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "tracking_info": "user123456",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "deposit_details": {
    "account_number": "16002600",
    "account_type": "checking",
    "routing_number": "123456789",
    "bank_name": "TD Bank"
  }
}

Responses

Status Object
200 DDS

DDS object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "tracking_info": "user123456",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "deposit_details": {
    "account_number": "16002600",
    "account_type": "checking",
    "routing_number": "123456789",
    "bank_name": "TD Bank"
  }
}

Data for the DDS report

Name Description
id string Unique ID
status string Request status
tracking_info string Any information passed to the Citadel Bridge from a partner
access_token string Access token for a Link to payroll provider
deposit_details object Bank account info. Used for Direct deposit and Funding account switching
» account_number string Account number
» account_type string Account type
» routing_number string Routing number
» bank_name string Bank name

Enumerated Values

Property Value Description
account_type checking Checking
account_type savings Savings

Funding account

Funding account switch (FAS) allows payroll administrators to change the financial account their payroll provider uses to fund their payroll. Citadel makes FAS easy to integrate into your system with a simple process.

  1. Make a request for a bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. to the Bridge token endpoint passing fas as the product_type and populating all properties in the account field with the account information for the new financial account.
  2. Display the Citadel BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. to the payroll administrator to allow them to log in to their payroll provider. When the administrator successfully logs in to their provider Citadel initiates the FAS process and returns a public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. through the onSuccess callback.
  3. Exchange the public_tokenA short-lived token used to exchange for an access_token from the backend. This token has a 6 hour expiration. for an access_tokenA private token unique to a single Link. Used to fetch Link data. and store this token in your database for Step 4.

    The payroll provider will send 2 micro deposits to the financial account specified in the bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration.. This part of the process can potentially take several days.

  4. Once the payroll administrator has provided your system with the values of the micro deposits (ie. via a form) use the access_tokenA private token unique to a single Link. Used to fetch Link data. stored to create a Refresh Task providing the micro deposit values in the micro_deposits array of the settings field in the request body. Citadel will provide these values to the payroll provider to complete the FAS process.

FAS report

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/account-switches/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/account-switches/";


      await PostAsync(null, url);

    }

    /// Performs a POST Request
    public async Task PostAsync(undefined content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(undefined content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/account-switches/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/account-switches/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/account-switches/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/account-switches/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint returns the FAS report.

HTTP Request

POST /account-switches/

Body parameter

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456"
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
access_token body string Access Token

Example responses

200 Response

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "tracking_info": "user123456",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "deposit_details": {
    "account_number": "16002600",
    "account_type": "checking",
    "routing_number": "123456789",
    "bank_name": "TD Bank",
    "settings": {
      "micro_deposits": [
        "0.1",
        "0.2"
      ]
    }
  }
}

Responses

Status Object
200 FAS

FAS object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "tracking_info": "user123456",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "deposit_details": {
    "account_number": "16002600",
    "account_type": "checking",
    "routing_number": "123456789",
    "bank_name": "TD Bank",
    "settings": {
      "micro_deposits": [
        "0.1",
        "0.2"
      ]
    }
  }
}

Data for the DDS report

Name Description
id string Unique ID
status string Request status
tracking_info string Any information passed to the Citadel Bridge from a partner
access_token string Access token for a Link to payroll provider
deposit_details object Bank account info. Used for Direct deposit and Funding account switching
» account_number string Account number
» account_type string Account type
» routing_number string Routing number
» bank_name string Bank name
» settings object (nullable) Used for Funding account switching
»» micro_deposits [string] Array with two micro deposit amounts

Enumerated Values

Property Value Description
account_type checking Checking
account_type savings Savings

Webhooks

Overview

There are multiple events that can occur in the Citadel platform outside of the normal API request/response process. A good example is when a TaskA process where a Link is used to pull data from a payroll provider. is being processed. When a successful payroll connection occurs (whether it's when a user logs in or a refresh task is created) there's a lot of data and documents that come from the payroll provider which need time to be processed. We don't want to delay the user experience or the API responses in these scenarios, so we've created webhooks to give developers the ability to get updates when the status of a TaskA process where a Link is used to pull data from a payroll provider. changes.

Webhooks are automated messages that are sent out by Citadel when an event occurs in the platform. By specifying an HTTP endpoint developers can be sent these messages to be notified of these events. Webhook requests are sent as an HTTP POST request to the webhook url specified.

Subscribing to webhooks

To subscribe to Citadel webhooks navigate to the Webhooks section of the dashboard and enter the URL endpoint where you would like to receive updates.

Webhook payload

There are many fields available when Citadel sends a request to a webhook endpoint and quite a few depend on the event that is being sent. Here's a list of the common fields that will occur in every call regardless of the event:

Name In Description
X-WEBHOOK-SIGN header A hash of the request body created with an Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request.
webhook_id body A unique identifier for this specific webhook request
event_type body An identifier of the event the webhook request is sent for

Events

Sample webhook payload

{
    "webhook_id": "609a82aab21e4d9ba2569f35e9e8f26a",
    "task_id": "67f2924530564282bbaf6d27655e94a4",
    "link_id": "64f8e374949c4b769706028022626bf1",
    "product": "income",
    "tracking_info": "27266f35-bb54-44c3-8905-070641a0c0aa",
    "event_type": "task-status-updated",
    "updated_at": "2021-04-26T13:02:20.369267+00:00",
    "status": "login"
}

For now the only event that we have that creates a webhook is task-status-updated, which occurs whenever the status of a TaskA process where a Link is used to pull data from a payroll provider. changes. When you receive a task-status-updated event with a status of done all the data for the TaskA process where a Link is used to pull data from a payroll provider. is downloaded and documents have been processed. You can use the link_id value to locate the access_tokenA private token unique to a single Link. Used to fetch Link data. in your system and retrieve the latest payroll data from the respective endpoint.

Field Name Description
task_id The identifier of the TaskA process where a Link is used to pull data from a payroll provider. associated to the event.
link_id The identifier of the LinkA connection to a payroll provider used to retrieve payroll data. associated to the TaskA process where a Link is used to pull data from a payroll provider.
product Which product the TaskA process where a Link is used to pull data from a payroll provider. is for (employment, income or admin)
tracking_info Any info passed into the bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. for the LinkA connection to a payroll provider used to retrieve payroll data.
updated_at The time the event occurred
status The new Task Status for the TaskA process where a Link is used to pull data from a payroll provider.

Webhook timing

While on the Citadel side webhook requests for Task statuses are sent in the same order the statuses are updated (ie. a full_parse event happens before a done event) there are many factors that can influence the delivery of webhook requests, such as network latency, outages, etc. As a result it's possible to not receive webhook events in the proper order so it's important when implementing your code to receive webhook requests from Citadel that you reference the updated_at field to track which events happened when.

Webhook security

Webhook signature creation

using System.Threading.Tasks;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using System.Text;
using System.Security.Cryptography;
using System;

namespace c_sharp.Controllers
{
  [ApiController]
  [Route("webhook")]
  public class WebhookController : ControllerBase
  {

    [HttpPost]
    public async Task<string> Post()
    {
      using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
      {
        string body = await reader.ReadToEndAsync();
        return generateWebhookSign(body, Environment.GetEnvironmentVariable("API_SECRET"));
      }
    }

    private string generateWebhookSign(string body, string key)
    {
      using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
      {
        // Compute the hash of the input file.
        byte[] hashValue = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));

        return "v1=" + BitConverter.ToString(hashValue).Replace("-", "").ToLower();

      }
    }
  }
}
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "strings"
)

func generate_webhook_sign(body string, key string) string {

    mac := hmac.New(sha256.New, []byte(key))
    mac.Write([]byte(body))
    return hex.EncodeToString(mac.Sum(nil))
}

func webhook(w http.ResponseWriter, r *http.Request) {
    b, _ := ioutil.ReadAll(r.Body)
    convertedBody := string(b)
    signature := generate_webhook_sign(convertedBody, os.Getenv("API_SECRET"))
    fullSignature := fmt.Sprintf("v1=%s", signature)

    fmt.Fprintf(w, fullSignature)
}
const crypto = require("crypto")

// ensure all request bodies are parsed to JSON. Callback function
// keeps a copy of the raw body for webhooks.
app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf
  }
}))

const generate_webhook_sign = (body, key) => {
  return crypto.createHmac("sha256", key)
  .update(body)
  .digest("hex")
}

app.post("/webhook", async (req, res) => {

  const body = req.rawBody.toString()
  const webhook_sign = generate_webhook_sign(body, API_SECRET)

  res.send(`v1=${webhook_sign}`).end()
})
import hashlib
import hmac

def generate_webhook_sign(payload: str, key: str) -> str:
    generated_hash = hmac.new(
        key=key.encode('utf-8'),
        msg=payload.encode('utf-8'),
        digestmod=hashlib.sha256,
    ).hexdigest()
    return f'v1={generated_hash}'

@app.route('/webhook', methods=['POST'])
def webhook():
    return generate_webhook_sign(request.data.decode('UTF-8'), secret)
class Webhook
  def self.generate_webhook_sign(body, key)
    digest = OpenSSL::Digest.new('sha256')
    return "v1=" + OpenSSL::HMAC.hexdigest(digest, key, body)
  end

  def self.post(body)
    return self.generate_webhook_sign(body, Citadel.client_secret)
  end
end

It's important in the endpoint that accepts our webhook that a deveoper can be sure the data is in fact coming from Citadel. In order for a developer to be able to verify this we've implemented webhook signatures. Every webhook request we send contains an X-WEBHOOK-SIGN header which is an HMAC hash of the request body using the developer's Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. as the hashing key and SHA-256 as the hashing function. We have code examples available for multiple languages, but here are some step by step instructions to verify the request is coming from us:

  1. Use a hash library to create an HMAC hash of the raw request body received by Citadel using the Access keyThe unique key passed into the X-Access-Secret header of every API request to authorize the request. you normally place in the X-Access-Secret header as the hashing key. Make sure to use SHA-256 as the hash function and that the final hash is converted to hexidecimal.

  2. Compare the hash created to the value in the X-WEBHOOK-SIGN header sent with the webhook request. If they match the developer can rest assured that Citadel is who sent the request and can continue process the webhook as needed.

Data removal

Code samples

import requests
headers = {
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.delete('/v1/link/{access_token}/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }




    /// Make a dummy request
    public async Task MakeDeleteRequest()
    {
      int id = 1;
      string url = "/v1/link/{access_token}/";

      await DeleteAsync(id, url);
    }

    /// Performs a DELETE Request
    public async Task DeleteAsync(int id, string url)
    {
        //Execute DELETE request
        HttpResponseMessage response = await Client.DeleteAsync(url + $"/{id}");

        //Return response
        await DeserializeObject(response);
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("DELETE", "/v1/link/{access_token}/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');

const headers = {
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/link/{access_token}/',
{
  method: 'DELETE',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.delete '/v1/link/{access_token}/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X DELETE /v1/link/{access_token}/ \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint removes a Link and all associated data.

HTTP Request

DELETE /link/{access_token}/

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
access_token path string Access token associated with Link
Status Object
200 None

Data refresh

Overview

From time to time your app may need to refresh the data from a verification or admin employee directory.

With the Refresh endpoints you can refresh the data from an existing LinkA connection to a payroll provider used to retrieve payroll data. without requiring the user to enter their payroll credentials a second time.

Performing a Refresh

To successfully perform a Refresh you need to follow these steps:

  1. Subscribe to Webhooks so when a TaskA process where a Link is used to pull data from a payroll provider. is completed (with a status of done) you can pull payroll data.
  2. Make a request to the /refresh/tasks endpoint passing in the access_tokenA private token unique to a single Link. Used to fetch Link data. from the original connection.
  3. Have your webhook endpoint handle changes to your TaskA process where a Link is used to pull data from a payroll provider.s status.

It's important to note that each Refresh TaskA process where a Link is used to pull data from a payroll provider. is billed.

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/refresh/tasks/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/refresh/tasks/";

      string json = @"{
  ""access_token"": ""48427a36d43c4d5aa6324bc06c692456"",
  ""settings"": {
    ""micro_deposits"": [
      ""0.1"",
      ""0.2""
    ]
  }
}";
      RefreshTaskCreateRequest content = JsonConvert.DeserializeObject(json);
      await PostAsync(content, url);


    }

    /// Performs a POST Request
    public async Task PostAsync(RefreshTaskCreateRequest content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(RefreshTaskCreateRequest content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/refresh/tasks/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "settings": {
    "micro_deposits": [
      "0.1",
      "0.2"
    ]
  }
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/refresh/tasks/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/refresh/tasks/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/refresh/tasks/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint creates a Link data refresh task.

HTTP Request

POST /refresh/tasks/

Body parameter

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "settings": {
    "micro_deposits": [
      "0.1",
      "0.2"
    ]
  }
}
Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
body body RefreshTaskCreateRequest Link data refresh task

Example responses

201 Response

{
  "task_id": "48427a36d43c4d5aa6324bc06c692456"
}
Status Object
201 RefreshTaskCreateResponse

RefreshTaskCreateRequest object

{
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "settings": {
    "micro_deposits": [
      "0.1",
      "0.2"
    ]
  }
}

Refresh Link data task

Name Description
access_token string Access token for a Link to payroll provider
settings object (nullable) Used for Funding account switching
» micro_deposits [string] Array with two micro deposit amounts

RefreshTaskCreateResponse object

{
  "task_id": "48427a36d43c4d5aa6324bc06c692456"
}

Response for refresh task creation endpoint.

Name Description
task_id string Unique ID for the Link data refresh task

Code samples

import requests
headers = {
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.get('/v1/refresh/tasks/{task_id}/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }

    /// Make a dummy request
    public async Task MakeGetRequest()
    {
      string url = "/v1/refresh/tasks/{task_id}/";
      var result = await GetAsync(url);
    }

    /// Performs a GET Request
    public async Task GetAsync(string url)
    {
        //Start the request
        HttpResponseMessage response = await Client.GetAsync(url);

        //Validate result
        response.EnsureSuccessStatusCode();

    }




    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/v1/refresh/tasks/{task_id}/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');

const headers = {
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/refresh/tasks/{task_id}/',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.get '/v1/refresh/tasks/{task_id}/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X GET /v1/refresh/tasks/{task_id}/ \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

This endpoint returns the refresh task status.

HTTP Request

GET /refresh/tasks/{task_id}/

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
task_id path string Task ID received from the POST request

Example responses

200 Response

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "refresh_date": "2020-03-10",
  "status": "new"
}

Responses

Status Object
200 RefreshTask

RefreshTask object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "refresh_date": "2020-03-10",
  "status": "new"
}

Link data refresh task

Name Description
id string Unique refresh task ID
refresh_date string(date) Date of the data refresh
status string Refresh task status

Authentication issues

Eventually the scenario is likely to occur where you attempt a data refresh and have authentication issues. But when do these occur and how do you deal with them?

When do authentication issues happen?

Data refresh authentication issues are likely to occur in two different scenarios. The first is a change in login credentials (likely a change in username or password). This situation will be evident by the /refresh/tasks/{task_id}/ endpoint returning a login_error value for the status field. The second situation is an error in mulit-factor authentication. This is likely to occur when Citadel is presented with an MFA question the user hasn't answered yet, or with MFA scenarios where a unique code is provided by an authenticator app or via email/SMS. You'll know when an MFA error occurs by the /refresh/tasks/{task_id}/ endpoint returning an mfa_error value for the status field.

Handling data refresh authentication issues

When you attempt a data refresh and are presented with either the login_error or mfa_error status, typically you would want to present the user with the chance to re-authenticate with their payroll provider. To do this, you need to create a new bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. by calling the /bridge_tokens/ endpoint passing the same access_tokenA private token unique to a single Link. Used to fetch Link data. used when attempting to perform a data refresh in the body of the /bridge_tokens/ request. Initializing BridgeThe client-side component that your users will interact with in order to link their payroll accounts to Citadel and allow you to access their accounts via the Citadel API. with this new bridge_tokenA short-lived token provided by the Citadel API to authorize the use of Bridge. This token has a 6 hour expiration. will take the applicant through the re-authentication process so your application can access the most recent data provided by the payroll provider. It's important to note that when the applicant re-authenticates this also pulls the latest data from the payroll provider.

Orders

Overview

There are instances where a developer may want to have a user connect to their payroll provider but doesn't have an application available for the user to log into. At Citadel we have a portal that allows developers to have a user to log into their payroll provider through a Citadel portal instead of in the developer's application. In order for this use case to happen an Order needs to be created. Through the API you can create an order and then send the share_url to the user to connect to their payroll provider or have Citadel send an email to the user to complete the process. Orders are only available for employment and income verification at this time.

Code samples

import requests
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.post('/v1/orders/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }


    /// Make a dummy request
    public async Task MakePostRequest()
    {
      string url = "/v1/orders/";

      string json = @"{
  ""verification_type"": ""employment"",
  ""first_name"": ""John"",
  ""last_name"": ""Doe"",
  ""email"": ""user@example.com"",
  ""phone"": ""4155554193"",
  ""company_name"": ""your prospective employer"",
  ""employers"": [
    {
      ""start_date"": ""2019-08-24"",
      ""end_date"": ""2019-11-27"",
      ""company_name"": ""Facebook Demo"",
      ""company_address"": {
        ""street"": ""1 Hacker Way"",
        ""city"": ""Menlo Park"",
        ""state"": ""CA"",
        ""zip"": ""94025""
      },
      ""company_domain"": ""https://www.facebook.com"",
      ""company_google_place_id"": ""0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB"",
      ""company_logo"": ""https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png""
    }
  ]
}";
      Order content = JsonConvert.DeserializeObject(json);
      await PostAsync(content, url);


    }

    /// Performs a POST Request
    public async Task PostAsync(Order content, string url)
    {
        //Serialize Object
        StringContent jsonContent = SerializeObject(content);

        //Execute POST request
        HttpResponseMessage response = await Client.PostAsync(url, jsonContent);
    }



    /// Serialize an object to Json
    private StringContent SerializeObject(Order content)
    {
        //Serialize Object
        string jsonObject = JsonConvert.SerializeObject(content);

        //Create Json UTF8 String Content
        return new StringContent(jsonObject, Encoding.UTF8, "application/json");
    }

    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Content-Type": []string{"application/json"},
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("POST", "/v1/orders/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');
const inputBody = {
  "verification_type": "employment",
  "first_name": "John",
  "last_name": "Doe",
  "email": "user@example.com",
  "phone": "4155554193",
  "company_name": "your prospective employer",
  "employers": [
    {
      "start_date": "2019-08-24",
      "end_date": "2019-11-27",
      "company_name": "Facebook Demo",
      "company_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "zip": "94025"
      },
      "company_domain": "https://www.facebook.com",
      "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
      "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png"
    }
  ]
};
const headers = {
  'Content-Type':'application/json',
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/orders/',
{
  method: 'POST',
  body: JSON.stringify(inputBody),
  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Content-Type' => 'application/json',
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.post '/v1/orders/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X POST /v1/orders/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

The following endpoint creates an Order:

HTTP Request

POST /orders/

Body parameter

{
  "verification_type": "employment",
  "first_name": "John",
  "last_name": "Doe",
  "email": "user@example.com",
  "phone": "4155554193",
  "company_name": "your prospective employer",
  "employers": [
    {
      "start_date": "2019-08-24",
      "end_date": "2019-11-27",
      "company_name": "Facebook Demo",
      "company_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "zip": "94025"
      },
      "company_domain": "https://www.facebook.com",
      "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
      "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png"
    }
  ]
}

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
body body Order Order for verification

Example responses

201 Response

{
  "id": "39aa1486ccca4bc19cda071ffc1ba392",
  "verification_type": "employment",
  "source": "internal",
  "order_number": "1534332",
  "company_name": "Amazon",
  "client_name": "Unnamed Verifications Inc.",
  "first_name": "John",
  "last_name": "Doe",
  "share_url": "https://cdn.citadelid.com/employment.html?bridge_token=63b4af88facb40e48f517c1e8c7abdf4&order_group_id=39aa1486ccca4bc19cda071ffc1ba392",
  "created_at": "2021-04-21T21:45:14.418542Z",
  "canceled_at": "2021-04-22T21:45:14.418542Z",
  "expired_at": "2021-04-24T21:45:14.418542Z",
  "is_expired": true,
  "employers": [
    {
      "id": "ad9f14440d624ec3b0f66e81e44518c7",
      "suborder_number": "133982343355",
      "status": "pending",
      "start_date": "2019-08-24",
      "end_date": "2019-11-27",
      "bridge_token": "e4100fccdae94691b4414c7306220c06",
      "company_name": "Facebook Demo",
      "company_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "zip": "94025"
      },
      "company_domain": "https://www.facebook.com",
      "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
      "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png",
      "created_at": "2021-04-21T22:12:59.346109Z",
      "employments": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "job_title": "PR associate",
          "job_type": "F",
          "start_date": "2018-01-01",
          "end_date": "2019-08-24",
          "dates_from_statements": false,
          "profile": {
            "first_name": "John",
            "last_name": "Doe",
            "middle_initials": "K",
            "ssn": "123456789",
            "email": "john.doe@example.com",
            "date_of_birth": "1992-03-03",
            "home_address": {
              "street": "1 Morgan Ave",
              "city": "Los Angeles",
              "state": "CA",
              "zip": "90210"
            }
          },
          "company": {
            "name": "Facebook Demo",
            "address": {
              "street": "1 Hacker Way",
              "city": "Menlo Park",
              "state": "CA",
              "zip": "94025"
            },
            "phone": "6503087300"
          },
          "statements": [
            {
              "id": "24d7e80942ce4ad58a93f70ce4115f5c",
              "pay_date": "2018-05-15",
              "net_pay": "11500.32",
              "net_pay_ytd": "31980.64",
              "gross_pay": "13900.11",
              "gross_pay_ytd": "49200.00",
              "bonus": "100.00",
              "commission": "12000.00",
              "hours": "40.00",
              "basis_of_pay": "S",
              "period_start": "2018-05-01",
              "period_end": "2018-05-15",
              "regular": "1695.11",
              "regular_ytd": "23000.00",
              "bonus_ytd": "1000.00",
              "commission_ytd": "24000.00",
              "overtime": "45.00",
              "overtime_ytd": "500.00",
              "other_pay": "60.00",
              "other_pay_ytd": "700.00",
              "earnings": {},
              "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
              "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
            }
          ],
          "bank_accounts": [
            {
              "account_number": "1234567890",
              "routing_number": "123456789",
              "account_name": "My Bank",
              "account_type": "C",
              "deposit_type": "A",
              "deposit_value": "200.00",
              "bank_name": "TD Bank"
            }
          ]
        }
      ],
      "feedbacks": [
        {
          "id": "24d7e80912ce4ab58a33f70ae4115f52",
          "created_at": "2021-04-21T21:45:14.418542Z",
          "type": "free_form",
          "text": "My company doesn't use a provider"
        }
      ]
    }
  ]
}

Responses

Status Object
201 OrderResponse

Order object

{
  "verification_type": "employment",
  "first_name": "John",
  "last_name": "Doe",
  "email": "user@example.com",
  "phone": "4155554193",
  "company_name": "your prospective employer",
  "employers": [
    {
      "start_date": "2019-08-24",
      "end_date": "2019-11-27",
      "company_name": "Facebook Demo",
      "company_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "zip": "94025"
      },
      "company_domain": "https://www.facebook.com",
      "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
      "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png"
    }
  ]
}

Name Description
verification_type string Type of the product (income or employment)
first_name string First name
last_name string Last name
email string(email) (nullable) Subject's email
phone string (nullable) Subject's phone number
company_name string (nullable) Prospective employer name
employers [EmployerCreate] (nullable) List of employers

Enumerated Values

Property Value Description
verification_type employment Employment verification
verification_type income Income verification

EmployerCreate object

{
  "start_date": "2019-08-24",
  "end_date": "2019-11-27",
  "company_name": "Facebook Demo",
  "company_address": {
    "street": "1 Hacker Way",
    "city": "Menlo Park",
    "state": "CA",
    "zip": "94025"
  },
  "company_domain": "https://www.facebook.com",
  "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
  "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png"
}

Name Description
start_date string(date) (nullable) Job start date
end_date string(date) (nullable) Job end date
company_name string (nullable) Company name
company_address object (nullable) Company address
» street string Company location street
» city string Company location city
» state string Company location state
» zip string Company location zip code
company_domain string (nullable) Company website domain
company_google_place_id string (nullable) Company Google Place identifier
company_logo string (nullable) Company logo URL

Code samples

import requests
headers = {
  'Accept': 'application/json',
  'X-Access-Client-Id': '',
  'X-Access-Secret': ''
}

r = requests.get('/v1/orders/{id}/', headers = headers)

print(r.json())

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

/// <<summary>>
/// Example of Http Client
/// <</summary>>
public class HttpExample
{
    private HttpClient Client { get; set; }

    /// <<summary>>
    /// Setup http client
    /// <</summary>>
    public HttpExample()
    {
      Client = new HttpClient();
    }

    /// Make a dummy request
    public async Task MakeGetRequest()
    {
      string url = "/v1/orders/{id}/";
      var result = await GetAsync(url);
    }

    /// Performs a GET Request
    public async Task GetAsync(string url)
    {
        //Start the request
        HttpResponseMessage response = await Client.GetAsync(url);

        //Validate result
        response.EnsureSuccessStatusCode();

    }




    /// Deserialize object from request response
    private async Task DeserializeObject(HttpResponseMessage response)
    {
        //Read body 
        string responseBody = await response.Content.ReadAsStringAsync();

        //Deserialize Body to object
        var result = JsonConvert.DeserializeObject(responseBody);
    }
}

package main

import (
       "bytes"
       "net/http"
)

func main() {

    headers := map[string][]string{
        "Accept": []string{"application/json"},
        "X-Access-Client-Id": []string{""},
        "X-Access-Secret": []string{""},
    }

    data := bytes.NewBuffer([]byte{jsonReq})
    req, err := http.NewRequest("GET", "/v1/orders/{id}/", data)
    req.Header = headers

    client := &http.Client{}
    resp, err := client.Do(req)
    // ...
}

const fetch = require('node-fetch');

const headers = {
  'Accept':'application/json',
  'X-Access-Client-Id':'',
  'X-Access-Secret':''
};

fetch('/v1/orders/{id}/',
{
  method: 'GET',

  headers: headers
})
.then(function(res) {
    return res.json();
}).then(function(body) {
    console.log(body);
});

require 'rest-client'
require 'json'

headers = {
  'Accept' => 'application/json',
  'X-Access-Client-Id' => '',
  'X-Access-Secret' => ''
}

result = RestClient.get '/v1/orders/{id}/',
  params: {
  }, headers: headers

p JSON.parse(result)

# You can also use wget
curl -X GET /v1/orders/{id}/ \
  -H 'Accept: application/json' \
  -H 'X-Access-Client-Id: ' \
  -H 'X-Access-Secret: '

The following endpoint returns the order.

HTTP Request

GET /orders/{id}/

Request Fields

Name In Description
X-Access-Client-Id header string Client ID
X-Access-Secret header string Client Access Key
id path string Order ID received from the POST request

Example responses

200 Response

{
  "id": "39aa1486ccca4bc19cda071ffc1ba392",
  "verification_type": "employment",
  "source": "internal",
  "order_number": "1534332",
  "company_name": "Amazon",
  "client_name": "Unnamed Verifications Inc.",
  "first_name": "John",
  "last_name": "Doe",
  "share_url": "https://cdn.citadelid.com/employment.html?bridge_token=63b4af88facb40e48f517c1e8c7abdf4&order_group_id=39aa1486ccca4bc19cda071ffc1ba392",
  "created_at": "2021-04-21T21:45:14.418542Z",
  "canceled_at": "2021-04-22T21:45:14.418542Z",
  "expired_at": "2021-04-24T21:45:14.418542Z",
  "is_expired": true,
  "employers": [
    {
      "id": "ad9f14440d624ec3b0f66e81e44518c7",
      "suborder_number": "133982343355",
      "status": "pending",
      "start_date": "2019-08-24",
      "end_date": "2019-11-27",
      "bridge_token": "e4100fccdae94691b4414c7306220c06",
      "company_name": "Facebook Demo",
      "company_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "zip": "94025"
      },
      "company_domain": "https://www.facebook.com",
      "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
      "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png",
      "created_at": "2021-04-21T22:12:59.346109Z",
      "employments": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "job_title": "PR associate",
          "job_type": "F",
          "start_date": "2018-01-01",
          "end_date": "2019-08-24",
          "dates_from_statements": false,
          "profile": {
            "first_name": "John",
            "last_name": "Doe",
            "middle_initials": "K",
            "ssn": "123456789",
            "email": "john.doe@example.com",
            "date_of_birth": "1992-03-03",
            "home_address": {
              "street": "1 Morgan Ave",
              "city": "Los Angeles",
              "state": "CA",
              "zip": "90210"
            }
          },
          "company": {
            "name": "Facebook Demo",
            "address": {
              "street": "1 Hacker Way",
              "city": "Menlo Park",
              "state": "CA",
              "zip": "94025"
            },
            "phone": "6503087300"
          },
          "statements": [
            {
              "id": "24d7e80942ce4ad58a93f70ce4115f5c",
              "pay_date": "2018-05-15",
              "net_pay": "11500.32",
              "net_pay_ytd": "31980.64",
              "gross_pay": "13900.11",
              "gross_pay_ytd": "49200.00",
              "bonus": "100.00",
              "commission": "12000.00",
              "hours": "40.00",
              "basis_of_pay": "S",
              "period_start": "2018-05-01",
              "period_end": "2018-05-15",
              "regular": "1695.11",
              "regular_ytd": "23000.00",
              "bonus_ytd": "1000.00",
              "commission_ytd": "24000.00",
              "overtime": "45.00",
              "overtime_ytd": "500.00",
              "other_pay": "60.00",
              "other_pay_ytd": "700.00",
              "earnings": {},
              "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
              "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
            }
          ],
          "bank_accounts": [
            {
              "account_number": "1234567890",
              "routing_number": "123456789",
              "account_name": "My Bank",
              "account_type": "C",
              "deposit_type": "A",
              "deposit_value": "200.00",
              "bank_name": "TD Bank"
            }
          ]
        }
      ],
      "feedbacks": [
        {
          "id": "24d7e80912ce4ab58a33f70ae4115f52",
          "created_at": "2021-04-21T21:45:14.418542Z",
          "type": "free_form",
          "text": "My company doesn't use a provider"
        }
      ]
    }
  ]
}

Responses

Status Object
200 OrderResponse

OrderResponse object

{
  "id": "39aa1486ccca4bc19cda071ffc1ba392",
  "verification_type": "employment",
  "source": "internal",
  "order_number": "1534332",
  "company_name": "Amazon",
  "client_name": "Unnamed Verifications Inc.",
  "first_name": "John",
  "last_name": "Doe",
  "share_url": "https://cdn.citadelid.com/employment.html?bridge_token=63b4af88facb40e48f517c1e8c7abdf4&order_group_id=39aa1486ccca4bc19cda071ffc1ba392",
  "created_at": "2021-04-21T21:45:14.418542Z",
  "canceled_at": "2021-04-22T21:45:14.418542Z",
  "expired_at": "2021-04-24T21:45:14.418542Z",
  "is_expired": true,
  "employers": [
    {
      "id": "ad9f14440d624ec3b0f66e81e44518c7",
      "suborder_number": "133982343355",
      "status": "pending",
      "start_date": "2019-08-24",
      "end_date": "2019-11-27",
      "bridge_token": "e4100fccdae94691b4414c7306220c06",
      "company_name": "Facebook Demo",
      "company_address": {
        "street": "1 Hacker Way",
        "city": "Menlo Park",
        "state": "CA",
        "zip": "94025"
      },
      "company_domain": "https://www.facebook.com",
      "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
      "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png",
      "created_at": "2021-04-21T22:12:59.346109Z",
      "employments": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "job_title": "PR associate",
          "job_type": "F",
          "start_date": "2018-01-01",
          "end_date": "2019-08-24",
          "dates_from_statements": false,
          "profile": {
            "first_name": "John",
            "last_name": "Doe",
            "middle_initials": "K",
            "ssn": "123456789",
            "email": "john.doe@example.com",
            "date_of_birth": "1992-03-03",
            "home_address": {
              "street": "1 Morgan Ave",
              "city": "Los Angeles",
              "state": "CA",
              "zip": "90210"
            }
          },
          "company": {
            "name": "Facebook Demo",
            "address": {
              "street": "1 Hacker Way",
              "city": "Menlo Park",
              "state": "CA",
              "zip": "94025"
            },
            "phone": "6503087300"
          },
          "statements": [
            {
              "id": "24d7e80942ce4ad58a93f70ce4115f5c",
              "pay_date": "2018-05-15",
              "net_pay": "11500.32",
              "net_pay_ytd": "31980.64",
              "gross_pay": "13900.11",
              "gross_pay_ytd": "49200.00",
              "bonus": "100.00",
              "commission": "12000.00",
              "hours": "40.00",
              "basis_of_pay": "S",
              "period_start": "2018-05-01",
              "period_end": "2018-05-15",
              "regular": "1695.11",
              "regular_ytd": "23000.00",
              "bonus_ytd": "1000.00",
              "commission_ytd": "24000.00",
              "overtime": "45.00",
              "overtime_ytd": "500.00",
              "other_pay": "60.00",
              "other_pay_ytd": "700.00",
              "earnings": {},
              "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
              "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
            }
          ],
          "bank_accounts": [
            {
              "account_number": "1234567890",
              "routing_number": "123456789",
              "account_name": "My Bank",
              "account_type": "C",
              "deposit_type": "A",
              "deposit_value": "200.00",
              "bank_name": "TD Bank"
            }
          ]
        }
      ],
      "feedbacks": [
        {
          "id": "24d7e80912ce4ab58a33f70ae4115f52",
          "created_at": "2021-04-21T21:45:14.418542Z",
          "type": "free_form",
          "text": "My company doesn't use a provider"
        }
      ]
    }
  ]
}

Name Description
id string Unique ID
verification_type string Type of the product (income or employment)
source string Type of the platform (internal or accio)
order_number string (nullable) External ID
company_name string Prospective employer name
client_name string Verification agency name
first_name string First name
last_name string Last name
share_url string Verification URL to share
created_at string Date and time when order was created
canceled_at string (nullable) Date and time when order was created
expired_at string Date and time when order would expire
is_expired boolean If order is already expired
employers [EmployerResponse] List of employers

Enumerated Values

Property Value Description
verification_type employment Employment verification
verification_type income Income verification
source internal Order created via API or Citadel Dashboard
source accio Order created via Accio 3rd party

EmployerResponse object

{
  "id": "ad9f14440d624ec3b0f66e81e44518c7",
  "suborder_number": "133982343355",
  "status": "pending",
  "start_date": "2019-08-24",
  "end_date": "2019-11-27",
  "bridge_token": "e4100fccdae94691b4414c7306220c06",
  "company_name": "Facebook Demo",
  "company_address": {
    "street": "1 Hacker Way",
    "city": "Menlo Park",
    "state": "CA",
    "zip": "94025"
  },
  "company_domain": "https://www.facebook.com",
  "company_google_place_id": "0ahUmEwjyn5WuspCwAhVOCc0KHd35BlgQ0JcGCA4oCzAB",
  "company_logo": "https://citadelid-resources.s3-us-west-2.amazonaws.com/facebook.png",
  "created_at": "2021-04-21T22:12:59.346109Z",
  "employments": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "job_title": "PR associate",
      "job_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "dates_from_statements": false,
      "profile": {
        "first_name": "John",
        "last_name": "Doe",
        "middle_initials": "K",
        "ssn": "123456789",
        "email": "john.doe@example.com",
        "date_of_birth": "1992-03-03",
        "home_address": {
          "street": "1 Morgan Ave",
          "city": "Los Angeles",
          "state": "CA",
          "zip": "90210"
        }
      },
      "company": {
        "name": "Facebook Demo",
        "address": {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025"
        },
        "phone": "6503087300"
      },
      "statements": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "pay_date": "2018-05-15",
          "net_pay": "11500.32",
          "net_pay_ytd": "31980.64",
          "gross_pay": "13900.11",
          "gross_pay_ytd": "49200.00",
          "bonus": "100.00",
          "commission": "12000.00",
          "hours": "40.00",
          "basis_of_pay": "S",
          "period_start": "2018-05-01",
          "period_end": "2018-05-15",
          "regular": "1695.11",
          "regular_ytd": "23000.00",
          "bonus_ytd": "1000.00",
          "commission_ytd": "24000.00",
          "overtime": "45.00",
          "overtime_ytd": "500.00",
          "other_pay": "60.00",
          "other_pay_ytd": "700.00",
          "earnings": {},
          "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
          "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
        }
      ],
      "bank_accounts": [
        {
          "account_number": "1234567890",
          "routing_number": "123456789",
          "account_name": "My Bank",
          "account_type": "C",
          "deposit_type": "A",
          "deposit_value": "200.00",
          "bank_name": "TD Bank"
        }
      ]
    }
  ],
  "feedbacks": [
    {
      "id": "24d7e80912ce4ab58a33f70ae4115f52",
      "created_at": "2021-04-21T21:45:14.418542Z",
      "type": "free_form",
      "text": "My company doesn't use a provider"
    }
  ]
}

Name Description
id string Unique ID
suborder_number string (nullable) External ID
status string Code for status (pending, sent, completed, error, canceled, expired)
start_date string (nullable) Job start date
end_date string (nullable) Job start date
bridge_token string (nullable) UUID value of bridge token
company_name string (nullable) Company name
company_address object (nullable) Company address
» street string Company location street
» city string Company location city
» state string Company location state
» zip string Company location zip code
company_domain string (nullable) Company website domain
company_google_place_id string (nullable) Company Google Place identifier
company_logo string (nullable) Company logo URL
created_at string Date and time when order was created
employments [EmploymentItem] List of employments
feedbacks [Feedback] List of feedbacks

Enumerated Values

Property Value Description
status pending The verification is being processed by Citadel to be available for the applicant
status sent An email has been sent to the applicant
status completed The applicant has completed the verification
status error An error occurred while the applicant was attempting to complete the verification
status canceled The verification was canceled
status expired The expiration time has passed and the verification is no longer valid

EmploymentItem object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "job_title": "PR associate",
  "job_type": "F",
  "start_date": "2018-01-01",
  "end_date": "2019-08-24",
  "dates_from_statements": false,
  "profile": {
    "first_name": "John",
    "last_name": "Doe",
    "middle_initials": "K",
    "ssn": "123456789",
    "email": "john.doe@example.com",
    "date_of_birth": "1992-03-03",
    "home_address": {
      "street": "1 Morgan Ave",
      "city": "Los Angeles",
      "state": "CA",
      "zip": "90210"
    }
  },
  "company": {
    "name": "Facebook Demo",
    "address": {
      "street": "1 Hacker Way",
      "city": "Menlo Park",
      "state": "CA",
      "zip": "94025"
    },
    "phone": "6503087300"
  },
  "statements": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "pay_date": "2018-05-15",
      "net_pay": "11500.32",
      "net_pay_ytd": "31980.64",
      "gross_pay": "13900.11",
      "gross_pay_ytd": "49200.00",
      "bonus": "100.00",
      "commission": "12000.00",
      "hours": "40.00",
      "basis_of_pay": "S",
      "period_start": "2018-05-01",
      "period_end": "2018-05-15",
      "regular": "1695.11",
      "regular_ytd": "23000.00",
      "bonus_ytd": "1000.00",
      "commission_ytd": "24000.00",
      "overtime": "45.00",
      "overtime_ytd": "500.00",
      "other_pay": "60.00",
      "other_pay_ytd": "700.00",
      "earnings": {},
      "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
      "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
    }
  ],
  "bank_accounts": [
    {
      "account_number": "1234567890",
      "routing_number": "123456789",
      "account_name": "My Bank",
      "account_type": "C",
      "deposit_type": "A",
      "deposit_value": "200.00",
      "bank_name": "TD Bank"
    }
  ]
}

Employment data

Name Description
id string Unique ID
job_title string (nullable) Employee's job title
job_type string (nullable) Employee's job type
start_date string(date) Employee's hire date
end_date string(date) (nullable) Employee's termination date
dates_from_statements boolean (nullable) Whether or not the Employee's hire and/or termination dates were derived from first/last pay statements
profile Profile Person's identity information
company Company Company information
statements [Statement] (nullable) List of paystubs received from a payroll provider
bank_accounts [BankAccount] (nullable) List of bank accounts linked to the employment

Enumerated Values

Property Value Description
job_type F Full Time
job_type P Part Time
job_type S Seasonal
job_type D Daily (per diem)
job_type C Contract

Errors

    Response
    application/json

    400 Bad request
    {
        "error":
        {
            "code": "validation_error",
            "message": "Validation error",
            "extra":
            {
                "invalid-params":
                [
                    {
                        "field": "first_name",
                        "message": "First name is required"
                    },
                    {
                        "field": "last_name",
                        "message": "Last name is required"
                    }
                ]
            }
        }
    }

    401 Unauthorized
    {
        "error":
        {
            "code": "expired_token",
            "message": "Public token expired: 48427a36d43c4d5aa6324bc06c692456"
        }
    }

    403 Forbidden
    {
        "error":
        {
            "code": "incorrect_token",
            "message": "Token is invalid"
        }
    }

HTTP response codes

Code Title Description
200 OK The request was successful.
400 Bad Request The request was unacceptable.
401 Unauthorized Missing or invalid credentials.
403 Forbidden Permission denied.
404 Not Found The resource does not exist.
50X Internal Server Error An error occurred with our API.

Application level codes

Bridge error types

Name Description
LINK_ERROR A generic error type has occurred

Bridge error codes

Name Description
NO_DATA User login was successful but there was no data inside the account
UNAVAILABLE The payroll provider is unreachable at the time of connection.
MFA_ERROR The wrong input was given for the MFA question
LOGIN_ERROR The username and/or password was incorrect
ERROR A generic error occurred

API Reference

Task statuses

Name Description
new The TaskA process where a Link is used to pull data from a payroll provider. has just been created
login Attempting to log into the payroll provider
mfa Attempting to complete multi-factor authentication
parse Attempting to parse payroll data
full_parse Initial parse is done. Parsing of slow objects is in progress.
done TaskA process where a Link is used to pull data from a payroll provider. is complete
login_error There was an error logging in
mfa_error There was an error completing authentication
config_error There is a configuration issue which needs to be resolved by the user or developer before we can complete the payroll connection.
account_locked The payroll account is locked
no_data No data available from the payroll provider
unavailable Payroll provider is unavailable
error Generic error during connection

Company object

{
  "name": "Facebook Demo",
  "address": {
    "street": "1 Hacker Way",
    "city": "Menlo Park",
    "state": "CA",
    "zip": "94025"
  },
  "phone": "6503087300"
}

Company

Name Description
name string Company name
address object (nullable) Company address
» street string (nullable) Company location street
» city string (nullable) Company location city
» state string (nullable) Company location state
» zip string (nullable) Company location zip code
phone string (nullable) Company phone number

Profile object

{
  "first_name": "John",
  "last_name": "Doe",
  "middle_initials": "K",
  "ssn": "123456789",
  "email": "john.doe@example.com",
  "date_of_birth": "1992-03-03",
  "home_address": {
    "street": "1 Morgan Ave",
    "city": "Los Angeles",
    "state": "CA",
    "zip": "90210"
  }
}

Profile

Name Description
first_name string First name
last_name string Last name
middle_initials string (nullable) Middle initials
ssn string (nullable) Social security number (Full or last 4 digits)
email string(email) (nullable) Email address
date_of_birth string(date) (nullable) Date of birth
home_address object (nullable) Home address
» street string (nullable) Street
» city string (nullable) City
» state string (nullable) State
» zip string (nullable) Zip code

Statement object

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "pay_date": "2018-05-15",
  "net_pay": "11500.32",
  "net_pay_ytd": "31980.64",
  "gross_pay": "13900.11",
  "gross_pay_ytd": "49200.00",
  "bonus": "100.00",
  "commission": "12000.00",
  "hours": "40.00",
  "basis_of_pay": "S",
  "period_start": "2018-05-01",
  "period_end": "2018-05-15",
  "regular": "1695.11",
  "regular_ytd": "23000.00",
  "bonus_ytd": "1000.00",
  "commission_ytd": "24000.00",
  "overtime": "45.00",
  "overtime_ytd": "500.00",
  "other_pay": "60.00",
  "other_pay_ytd": "700.00",
  "earnings": {},
  "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
  "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
}

Pay stub data

Name Description
id string (nullable) Unique ID
pay_date string(date) Pay Date
net_pay string(decimal) (nullable) Net pay
net_pay_ytd string(decimal) (nullable) Net pay year to date
gross_pay string(decimal) (nullable) Gross pay
gross_pay_ytd string(decimal) (nullable) Gross pay year to date
bonus string(decimal) (nullable) Bonus
commission string(decimal) (nullable) Commission
hours string(decimal) (nullable) Work hours during a pay period
basis_of_pay string (nullable) Basis of pay
period_start string(date) (nullable) Period start
period_end string(date) (nullable) Period end
regular string(decimal) (nullable) Regular pay
regular_ytd string(decimal) (nullable) Regular salary year to date
bonus_ytd string(decimal) (nullable) Bonus year to date
commission_ytd string(decimal) (nullable) Commission year to date
overtime string(decimal) (nullable) Overtime pay
overtime_ytd string(decimal) (nullable) Overtime pay year to date
other_pay string(decimal) (nullable) All other pays
other_pay_ytd string(decimal) (nullable) All other pays year to date
earnings object (nullable) Earnings this pay cycle by type (see Earnings object)
md5sum string(string) (nullable) MD5 hash value computed based on the file content
file string(uri) (nullable) Link to a pay stub file (format is specified in the content-type)

Enumerated Values

Property Value Description
basis_of_pay S Salary
basis_of_pay H Hourly
basis_of_pay W Weekly
{
  "Additional Earnings": 0.0,
  "Loan Forgiven": 150.50,
  "Gross Earnings": 8333.33,
}

Earnings object

The earnings field of the Statement object is a dynamic object that contains all earnings in a dynamic and unstructured format. To the right is an example of what the earnings object could look like, but we cannot guarantee any field name being avaiable for this object.

BankAccount object

{
  "account_number": "1234567890",
  "routing_number": "123456789",
  "account_name": "My Bank",
  "account_type": "C",
  "deposit_type": "A",
  "deposit_value": "200.00",
  "bank_name": "TD Bank"
}

Bank account

Name Description
account_number string Account number
routing_number string (nullable) Routing number
account_name string (nullable) User friendly account name
account_type string (nullable) Account type
deposit_type string (nullable) Deposit type
deposit_value string(decimal) (nullable) Deposit value
bank_name string (nullable) Bank name

Enumerated Values

Property Value Description
account_type C Checking
account_type S Savings
deposit_type E Entire
deposit_type P Percent
deposit_type A Amount

Feedback object

{
  "id": "24d7e80912ce4ab58a33f70ae4115f52",
  "created_at": "2021-04-21T21:45:14.418542Z",
  "type": "free_form",
  "text": "My company doesn't use a provider"
}

Name Description
id string Unique ID
created_at string Date and time when feedback was created
type string Type of feedback (dont_know_provider, dont_know_credentials, free_form, no_feedback)
text string (nullable) Plain text of feedback

Enumerated Values

Property Value Description
type dont_know_provider The applicant doesn't know their payroll provider
type dont_know_credentials The applicant doesn't know their payroll login credentials
type free_form The applicant has left their own feedback
type no_feedback The applicant left no feedback

Changelog

Overview

The Citadel API is always evolving. Below we'll highlight the major changes so you can keep up to date with feature adds, endpoint adjustments and any other important changes to our API.

May 3, 2021

April 6, 2021

March 22, 2021

March 16, 2021

March 1, 2021

February 25, 2021

January 28, 2021