Exporting API Consumer Billing Data

Export usage data to bill API consumers using your enterprise's billing platform.

๐Ÿ“˜

Enterprise Hub customers only

This document does not apply to rapidapi.com users.

Overview

For Enterprise Hub customers who want to monetize their APIs while managing invoicing and payment collections outside of the Enterprise Hub, Rapid can enable the option to export quota usage reports and subscription details. The exported files are in CSV format. This allows your enterprise to parse and feed the required information into your existing billing platforms to collect payments from consumers.

The following files are related to exporting usage data:

  1. quotas.csv - An export of the details about quota usage of different subscriptions.
  2. subscriptions.csv - An export of the subscriptions that need to be invoiced for a given timeframe.

Exporting the data

The quota and subscription information should be retrieved using the GraphQL Platform API, from the personal context of an Environment Admin. Obtaining the quotas.csv and/or subscriptions.csv files is a two-step process:

  1. The generateFile mutation is used to create the latest quotas.csv and/or subscriptions.csv files.
  2. The getFileDetails query is used to download the files.

1. Generating the CSV files

Use the generateFile mutation to generate the quota and/or subscription files. In the Variables, set the actionName to either SUBSCRIPTIONS or QUOTAS. The from and to dates can be provided in ISO 8601 format (as shown below) or in UNIX time. The date range can not be larger than 31 days. Data is only available for the most recent 90 days.

The returned action_status value can be PENDING, IN_PROGRESS, SUCCESS, or FAILED. You can download the file once the status is SUCCESS. The returned id is needed to download the file.

mutation generateFile($input: generateFileInput!) {
  generateFile(input: $input) {
		id
		action_name
		action_status
		result_file_link
  }
}
{
  "input": {
    "from": "2023-09-01T00:00:00.000Z",
    "to": "2023-09-30T23:59:59.999Z",
    "actionName": "SUBSCRIPTIONS"
  }
}

๐Ÿšง

Limitations for the generateFile mutation

  1. The date range can not be larger than 31 days.
  2. Data is only available for the most recent 90 days.
  3. There will be a limitation of 10 successful calls to generateFile per calendar month. When the limit is exceeded, the request will be denied with a clear error.
  4. Once a file is generated, it is available to be downloaded for a period of 5 days.
  5. The SUBSCRIPTIONS export will only contain the following subscriptions:
    • All active subscriptions in the timeframe (created during or prior to the timeframe).
    • Subscriptions that were deleted in the timeframe.

2. Downloading the CSV files

Once a file has been generated (see above) and has returned an action_status of SUCCESS, you can use the getFileDetails query to download it. In the Variables, you must pass the id returned from the generateFile mutation.

query getFileDetails($id: ID!) {
  getFileDetails(id: $id) {
		id
		action_name
		action_status
		result_file_link
  }
} 

{
  "id": "id_that_was_provided_in_previous_mutation_a16a5e74-35e3-4ed8..."
}

Sample: Constructing an invoice from the data

The following is a Node.js program that suggests how to combine CSV files above into JSON formatted data. For each user or team, the JSON lists the subscriptions, price, API name, and any overages.

Note: This is only a suggested implementation that can be further tweaked and customized to your specific needs.

const fs = require('fs');
const csv = require("csvtojson");

const calculateOverCharge = (
  subscription,
  quotasBySubscriptionId
) => {
  const {
    subscription_id,
    object_id,
    object_limit_type,
    object_name,
    object_overage_cost,
    object_quota_limit,
    object_quota_type,
  } = subscription;

  const currentObjectQuotas = quotasBySubscriptionId[subscription_id]?.[object_id];

  if (!currentObjectQuotas) {
    return
  }
  // if overCharge is zero there is no sense to calculate it
  if (Number(object_overage_cost) === 0) {
    return;
  }

  let over_usage_amount = 0;
  let over_usage_total_price = 0;

  if (object_quota_type === 'MONTHLY') {
    const sumOfAllQuotas = currentObjectQuotas.reduce((result, currentQuota) => {
      return result + Number(currentQuota.object_usage)
    }, 0);
    const delta = sumOfAllQuotas - Number(object_quota_limit)
    over_usage_amount = delta > 0 ? delta : 0;
    over_usage_total_price = over_usage_amount * Number(object_overage_cost);
  } else {
    const sumOfAllQuotas = currentObjectQuotas.reduce((result, currentQuota) => {
      const delta = Number(currentQuota.object_usage) - Number(object_quota_limit);

      return delta > 0 ? result + delta : result
    }, 0);

    over_usage_amount = sumOfAllQuotas;
    over_usage_total_price = over_usage_amount * Number(object_overage_cost);
  }

  // there is no over usage
  if (over_usage_amount === 0) {
    return;
  }

  return {
    object_id,
    object_limit_type,
    object_name,
    object_overage_cost: +object_overage_cost,
    object_quota_limit: +object_quota_limit,
    object_quota_type,
    over_usage_total_price,
    over_usage_amount,
  }
}

const run = async () => {
  const quotas = await csv().fromFile('./quotas.csv');

  const quotasBySubscriptionId = quotas.reduce((result, quota) => {
    const currentSubscription = result[quota.subscription_id];

    if (!quota.object_id) {
      return result;
    }

    return {
      ...result,
      [quota.subscription_id]: {
        ...currentSubscription,
        [quota.object_id]: [...(currentSubscription?.[quota.object_id] || []), quota],
      }
    }

  }, {});

  const subscriptions = await csv().fromFile('./subscriptions.csv');

  const subscriptionsByConsumerIDs = {};

  subscriptions.forEach(subscription => {
    const subscriptionConsumerID = subscription.subscription_owner_id;
    const subscriptionID = subscription.subscription_id;

    // consumer not yet populated in the subscriptionsByConsumerIDs object
    if (!subscriptionsByConsumerIDs[subscriptionConsumerID]) {
      const {
        // Subscription data
        subscription_owner_id,
        subscription_owner_org_email,
        subscription_owner_org_id,
        subscription_owner_org_name,
        subscription_owner_team_name,
        subscription_owner_type,
        subscription_owner_user_email,
        subscription_owner_user_name,
      } = subscription;

      subscriptionsByConsumerIDs[subscriptionConsumerID] = {
        consumerData: {
          subscription_owner_id,
          subscription_owner_org_email,
          subscription_owner_org_id,
          subscription_owner_org_name,
          subscription_owner_team_name,
          subscription_owner_type,
          subscription_owner_user_email,
          subscription_owner_user_name,
        },
        subscriptions: {},
      }
    }

    // the current subscription not yet populated in the subscriptions object
    if (!subscriptionsByConsumerIDs[subscriptionConsumerID].subscriptions[subscriptionID]) {
      const {
        // API data
        api_id,
        api_name,
        api_owner_id,
        api_owner_org_email,
        api_owner_org_id,
        api_owner_org_name,
        api_owner_team_name,
        api_owner_type,
        api_owner_user_email,
        api_owner_user_name,
        api_version_id,
        api_version_name,
        // Plan details
        api_billing_plan_name,
        api_billing_plan_plan_type,
        api_billing_plan_price,
        api_billing_plan_version_id,
        //subscription data
        subscription_created_date,
        subscription_deleted_date,
      } = subscription;

      subscriptionsByConsumerIDs[subscriptionConsumerID].subscriptions[subscriptionID] = {
        created_date: subscription_created_date,
        deleted_date: subscription_deleted_date,
        plan: {
          api_billing_plan_name,
          api_billing_plan_plan_type,
          api_billing_plan_price: api_billing_plan_price ? Number(api_billing_plan_price) : 0,
          api_billing_plan_version_id,
        },
        api: {
          api_id,
          api_name,
          api_owner_id,
          api_owner_org_email,
          api_owner_org_id,
          api_owner_org_name,
          api_owner_team_name,
          api_owner_type,
          api_owner_user_email,
          api_owner_user_name,
          api_version_id,
          api_version_name,
        },
        overUsage: []
      }
    }

    const calculatedOverCharge = calculateOverCharge(
      subscription,
      quotasBySubscriptionId
    )
    if (calculatedOverCharge) {
      subscriptionsByConsumerIDs[subscriptionConsumerID].subscriptions[subscriptionID].overUsage.push(calculatedOverCharge)
    }
  })
  return subscriptionsByConsumerIDs;
}

Suggested usage

  1. The API builder publishes APIs that include paid plans.
  2. The API will have a Free plan that has a strict quota, allowing Consumers to immediately gain access to the API for testing purposes.
  3. The paid plans have the "Request Approval" option enabled.
  4. The API consumer asks to subscribe to the API.
  5. The API builder and potential consumer agree on a payment method and sign an agreement.
  6. The API builder approves the subscription.
  7. The API consumer uses the API.
  8. On a monthly basis, an environment admin collects the quota data for the Enterprise Hub and processes it. (Or this process is automated.)
  9. Each API builder receives usage information for each of his subscribers and invoices the consumers directly.

Fields of the subscriptions CSV file

SectionColumn NameEnum ValuesDescription / Notes
API owner detailsapi_owner_idUnique id of the entity that owns the API
api_owner_typeUser, Team
api_owner_user_nameEmpty if owner is a team
api_owner_user_emailEmpty if owner is a team
api_owner_team_nameEmpty if owner is a user
api_owner_org_idEmpty if owner is a user
api_owner_org_nameEmpty if owner is a user
api_owner_org_emailEmpty if owner is a user
API detailsapi_id
api_name
api_version_id
api_version_name
subscription idsubscription_id
user detailssubscription_owner_idUnique id of the entity that owns the subscription
subscription_owner_typeUser, Team
subscription_owner_user_nameEmpty if owner is a team
subscription_owner_user_emailEmpty if owner is a team
subscription_owner_team_nameEmpty if owner is a user
subscription_owner_org_idEmpty if owner is a user
subscription_owner_org_nameEmpty if owner is a user
subscription_owner_org_emailEmpty if owner is a user
subscription pricing and usageapi_billing_plan_nameBASIC, PRO, CUSTOM-plan, etc.
api_billing_plan_price
api_billing_plan_typeMONTHLY, PERUSE
api_billing_plan_version_idValue such as billingplanversion_82b7ab3f-c67c-4886-a0fd-168b9dd822xx
subscription_statusACTIVE, DELETEDStat of the subscription at the time the exporting process took place
subscription_parent_idIf the subscription is for a team, this value contains the organization ID, such as 5755575
subscription_created_dateValue in ISO 8601 format
subscription_cancelled_date
subscription_deleted_date
object_idValue such as billingitem_6c62eea2-a22c-4b98-a926-e3d4ef0a84xx
object_nameBilling object name such as Requests or Email
object_limit_typesoft, hardEmpty if limit_period = UNLIMITED
object_overage_costPrice per billing item beyond the agreed limit. 0 means unlimited. Applies only for soft limits.
object_quota_limitInteger 0 if object_quota_type = UNLIMITEDBilling object quota limit value (e.g. max number of requests per day - not Rate Limit). 0 if object_quota_type = UNLIMITED
object_quota_typeUNLIMITED, MONTHLY, DAILYBilling object quota limit period

Fields of the quotas CSV file

SectionColumn NameEnum ValuesDescription / Notes
API detailsapi_id
api_name
api_version_id
api_version_name
subscription idsubscription_id
subscription pricing and usageapi_billing_plan_nameBASIC, PRO, CUSTOM-plan, etc.
api_billing_plan_version_idValue such as billingplanversion_82b7ab3f-c67c-4886-a0fd-168b9dd822xx
object_idValue such as billingitem_6c62eea2-a22c-4b98-a926-e3d4ef0a84xx
object_nameBilling object name such as Requests or Email
dateThe date when the API consumer made the call to the API
object_usageThe number of calls made to a specific object (requests, calls, tokens, coins, etc.).
Each api call may trigger multiple objects.

Notes

  1. Additional consideration is needed to allow using this process in conjunction with API plans that include hard limits.

Contact your Rapid representative for additional information.