RTBF custom script

To delete responses in a given form, use the Delete responses endpoint. To delete responses across multiple forms, for example to implement RTBF, consider writing a custom script, as explained on this page.

Note: Right now Typeform does not have an endpoint to retrieve or delete responses across multiple forms directly.

Authenticate to Typeform

Authenticate to Typeform with an access token. Read Get started.

Get all form ids of relevant forms

If you have a small number of relevant forms that may contain responses you're interested in deleting, consider copying their form ids directly from the web ui. Navigate to the form and copy the form id from the browser url. For example, in the case of:

https://admin.typeform.com/form/Ja5eDnFV/create?block=663f2d82-0132-4689-8824-26856bf2a41c

the form id is Ja5eDnFV.

If you have a large number of forms, get all relevant forms using the Retrieve forms endpoint. You may want to specify a workspace id to narrow your results. Otherwise, the endpoint will return all forms that your token can access. Get the form id of each returned form.

Consider writing to file the form ids you've retrieved in this step so that you break up your script into individually-executable steps. Also, since you're probably not going to be creating new forms very often, you only need to run this part whenever you create a new form.

Your script may look like:

Python

import requests

def get_form_ids(token):
  url = "https://api.typeform.com/forms"
  headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json"
  }

  form_ids = []
  page = 0
  while True:
    page += 1
    params = { "page_size": 200, "page": page }
    python_response = requests.get(url, headers=headers, params=params)
    items = python_response.json()["items"]
    if len(items) == 0:
      break
    [form_ids.append(item["id"]) for item in items]
  return form_ids

# main
form_ids = get_form_ids(token)
with open("form_ids", 'w') as f:
  for form_id in form_ids:
    f.write(f"{form_id}\n")
f.close()

Get matching responses and delete them

Typeform's API does not support deleting matching responses in one shot. You have to first get matching responses (in one endpoint), extract their response ids, and then delete those responses (in another endpoint) by passing in those response ids. To get matching responses, use the Retrieve responses endpoint. For example, if you want to ultimately delete all responses that have a given email address, you would pass that into the query query parameter. To delete responses, use the Delete responses endpoint, passing in the response ids of responses you want to delete, as part of the included_response_ids array in the request body.

Note: 🚨 Deleting responses is permanent and irreversible. Typeform Customer Support cannot recover deleted responses. Thoroughly test any script, such as using test forms and test responses. Also consider backing up your responses by sending them to an integration.

Consider the situation where you want to delete all responses that have an email address in emails.txt. And you've already extracted the relavant form ids (from the previous step) into form_ids.txt.

Your script may look like:

Python

import requests

attempts = 5

def get_matching_response_ids(token, form_id, email):
  for attempt in range(1, attempts+1):
    try:
      url = f"https://api.typeform.com/forms/{form_id}/responses"
      headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
        "Accept": "application/json"
      }
      response_ids = []
      before_response_id = ""
      while True:
        params = {
          "page_size": 1000,
          "before": before_response_id,
          "query": email
        }
        python_response = requests.get(url, headers=headers, params=params)
        items = python_response.json()["items"]    
        if len(items) == 0:
          break
        [response_ids.append(item["response_id"]) for item in items]
        before_response_id = response_ids[-1] # For next loop iteration      
      return response_ids
    except:
      print(f"🚨🚨🚨 exception caught in get_matching_response_ids on attempt {attempt} of {attempts}")
  raise Exception("🚨🚨🚨 attempts exceeded in get_matching_response_ids")

# main
with open("emails", "r") as f:
  data = f.read()
f.close()
emails = data.split("\n")

with open("form_ids", "r") as f:
  data = f.read()
f.close()
form_ids = data.split("\n")

delete_counts = {}
for email in emails:
  delete_count = 0
  for index, form_id in enumerate(form_ids):  
    response_ids = get_matching_response_ids(token, form_id, email)    
    if (len(response_ids) > 0):
      delete_status = delete_response_ids(token, form_id, response_ids)
      if (delete_status == "success"):
        delete_count += len(response_ids)
  delete_counts[email] = delete_count

If you have a large number of forms and emails, consider an optimization to shorten form_ids, reducing the inner loop iterations above. Prior to executing above, prune form_ids so that you only consider forms that are not stale, i.e have been actually collecting responses recently (which may be when you last ran this script). Leverage the since query parameter in the Retrieve responses endpoint.

Your script may look like:

Python

import requests

def get_form_ids_with_responses(token, form_ids):  
  headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json"
  }

  form_ids_with_responses = []
  for form_id in form_ids:
    url = f"https://api.typeform.com/forms/{form_id}/responses"
    python_response = requests.get(url, headers=headers)
    items = python_response.json()["items"]
    if len(items) > 0:      
      form_ids_with_responses.append(form_id)
  return form_ids_with_responses

Consider programming best practices such as catching errors and exceptions. In particular, wrap endpoint calls in exception handling (try again) code, defending against network or server glitches.