First, load the salesforcer
package and login. There are two ways to authenticate: 1) OAuth 2.0 and 2) Basic Username-Password. It is recommended to use OAuth 2.0 so that passwords do not have to be shared/embedded within scripts. User credentials will be stored in locally cached file entitled “.httr-oauth-salesforcer” in the current working directory.
suppressWarnings(suppressMessages(library(dplyr)))
-library(salesforcer)
-sf_auth()
Just a note, that it’s not necessary to setup your own Connected App in Salesforce to use OAuth 2.0 authentication. The only difference is that the authentication will be run through the client created and associated with the salesforcer
package. By using the package client, you will NOT be giving access to Salesforce to anyone, the package is just the medium for you to connect to your own data. If you wanted more control you would specify those options like so:
options(salesforcer.consumer_key = "012345678901-99thisisatest99connected33app22key")
-options(salesforcer.consumer_secret = "Th1s1sMyConsumerS3cr3t")
-
-sf_auth()
options(salesforcer.consumer_key = "012345678901-99thisisatest99connected33app22key")
+options(salesforcer.consumer_secret = "Th1s1sMyConsumerS3cr3t")
+
+sf_auth()
After logging in with sf_auth()
, you can check your connectivity by looking at the information returned about the current user. It should be information about you!
# pull down information of person logged in
-# it's a simple easy call to get started
-# and confirm a connection to the APIs
-user_info <- sf_user_info()
-sprintf("User Id: %s", user_info$id)
-#> character(0)
-sprintf("User Active?: %s", user_info$isActive)
-#> character(0)
# pull down information of person logged in
+# it's a simple easy call to get started
+# and confirm a connection to the APIs
+user_info <- sf_user_info()
+sprintf("User Id: %s", user_info$id)
+#> character(0)
+sprintf("User Active?: %s", user_info$isActive)
+#> character(0)
Salesforce has objects and those objects contain records. One default object is the “Contact” object. This example shows how to create two records in the Contact object.
- +Retrieve pulls down a specific set of records and fields. It’s very similar to running a query, but doesn’t use SOQL. Here is an example where we retrieve the data we just created.
-retrieved_records <- sf_retrieve(ids=created_records$id,
- fields=c("FirstName", "LastName"),
- object_name="Contact")
-retrieved_records
-#> # A tibble: 2 x 3
-#> Id FirstName LastName
-#> <chr> <chr> <chr>
-#> 1 0036A00000kHti1QAC Test Contact-Create-1
-#> 2 0036A00000kHti2QAC Test Contact-Create-2
retrieved_records <- sf_retrieve(ids=created_records$id,
+ fields=c("FirstName", "LastName"),
+ object_name="Contact")
+retrieved_records
+#> # A tibble: 2 x 3
+#> Id FirstName LastName
+#> <chr> <chr> <chr>
+#> 1 0036A00000tZzXCQA0 Test Contact-Create-1
+#> 2 0036A00000tZzXDQA0 Test Contact-Create-2
Salesforce has proprietary form of SQL called SOQL (Salesforce Object Query Language). SOQL is a powerful tool that allows you to return the attributes of records on almost any object in Salesforce including Accounts, Contacts, Tasks, Opportunities, even Attachments! Below is an example where we grab the data we just created including Account object information for which the Contact record is associated with. The Account column is all NA
since we have yet to provide information to link these Contacts with Accounts.
my_soql <- sprintf("SELECT Id,
- Account.Name,
- FirstName,
- LastName
- FROM Contact
- WHERE Id in ('%s')",
- paste0(created_records$id , collapse="','"))
-
-queried_records <- sf_query(my_soql)
-queried_records
-#> # A tibble: 2 x 4
-#> Id Account FirstName LastName
-#> * <chr> <lgl> <chr> <chr>
-#> 1 0036A00000kHti1QAC NA Test Contact-Create-1
-#> 2 0036A00000kHti2QAC NA Test Contact-Create-2
my_soql <- sprintf("SELECT Id,
+ Account.Name,
+ FirstName,
+ LastName
+ FROM Contact
+ WHERE Id in ('%s')",
+ paste0(created_records$id , collapse="','"))
+
+queried_records <- sf_query(my_soql)
+queried_records
+#> # A tibble: 2 x 4
+#> Id Account FirstName LastName
+#> <chr> <lgl> <chr> <chr>
+#> 1 0036A00000tZzXCQA0 NA Test Contact-Create-1
+#> 2 0036A00000tZzXDQA0 NA Test Contact-Create-2
After creating records you can update them using sf_update()
. Updating a record requires you to pass the Salesforce Id
of the record. Salesforce creates a unique 18-character identifier on each record and uses that to know which record to attach the update information you provide. Simply include a field or column in your update dataset called “Id” and the information will be matched. Here is an example where we update each of the records we created earlier with a new first name called “TestTest”.
# Update some of those records
-queried_records <- queried_records %>%
- mutate(FirstName = "TestTest") %>%
- select(-Account)
-
-updated_records <- sf_update(queried_records, object_name="Contact")
-updated_records
-#> # A tibble: 2 x 2
-#> id success
-#> <chr> <chr>
-#> 1 0036A00000kHti1QAC true
-#> 2 0036A00000kHti2QAC true
# Update some of those records
+queried_records <- queried_records %>%
+ mutate(FirstName = "TestTest") %>%
+ select(-Account)
+
+updated_records <- sf_update(queried_records, object_name="Contact")
+updated_records
+#> # A tibble: 2 x 2
+#> id success
+#> <chr> <lgl>
+#> 1 0036A00000tZzXCQA0 TRUE
+#> 2 0036A00000tZzXDQA0 TRUE
You can also delete records in Salesforce. The method implements a “soft” delete meaning that the deleted records go to the Recycle Bin which can be emptied or queried against later in the event that the record needed.
-deleted_records <- sf_delete(updated_records$id)
-deleted_records
-#> # A tibble: 2 x 3
-#> id success errors
-#> <chr> <lgl> <list>
-#> 1 0036A00000kHti1QAC TRUE <list [0]>
-#> 2 0036A00000kHti2QAC TRUE <list [0]>
Finally, Salesforce has a unique method called “upsert” that allows you to create and/or update records at the same time. More specifically, if the record is not found based an an “External Id” field, then Salesforce will create the record instead of updating one. Below is an example where we create 2 records, then upsert 3, where 2 are matched and updated and one is created. NOTE: You will need to create a custom field on the target object and ensure it is labeled as an “External Id” field. Read more at http://blog.jeffdouglas.com/2010/05/07/using-exernal-id-fields-in-salesforce/.
-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
- LastName = paste0("Contact-Create-", 1:n),
- My_External_Id__c=letters[1:n])
-created_records <- sf_create(new_contacts, "Contact")
-
-upserted_contacts <- tibble(FirstName = rep("Test", n),
- LastName = paste0("Contact-Upsert-", 1:n),
- My_External_Id__c=letters[1:n])
-new_record <- tibble(FirstName = "Test",
- LastName = paste0("Contact-Upsert-", n+1),
- My_External_Id__c=letters[n+1])
-upserted_contacts <- bind_rows(upserted_contacts, new_record)
-
-upserted_records <- sf_upsert(input_data=upserted_contacts,
- object_name="Contact",
- external_id_fieldname="My_External_Id__c")
-upserted_records
-#> # A tibble: 3 x 3
-#> created id success
-#> <chr> <chr> <chr>
-#> 1 false 0036A00000kHti6QAC true
-#> 2 false 0036A00000kHti7QAC true
-#> 3 true 0036A00000kHtiBQAS true
n <- 2
+new_contacts <- tibble(FirstName = rep("Test", n),
+ LastName = paste0("Contact-Create-", 1:n),
+ My_External_Id__c=letters[1:n])
+created_records <- sf_create(new_contacts, "Contact")
+
+upserted_contacts <- tibble(FirstName = rep("Test", n),
+ LastName = paste0("Contact-Upsert-", 1:n),
+ My_External_Id__c=letters[1:n])
+new_record <- tibble(FirstName = "Test",
+ LastName = paste0("Contact-Upsert-", n+1),
+ My_External_Id__c=letters[n+1])
+upserted_contacts <- bind_rows(upserted_contacts, new_record)
+
+upserted_records <- sf_upsert(input_data=upserted_contacts,
+ object_name="Contact",
+ external_id_fieldname="My_External_Id__c")
+upserted_records
+#> # A tibble: 3 x 3
+#> created id success
+#> <lgl> <chr> <lgl>
+#> 1 FALSE 0036A00000tZzXHQA0 TRUE
+#> 2 FALSE 0036A00000tZzXIQA0 TRUE
+#> 3 TRUE 0036A00000tZzXMQA0 TRUE
salesforcer supports OAuth 2.0 authentication which is preferred, but for backward compatibility provides the username-password authentication routine implemented by RForcecom. Here is an example running the function from each of the packages side-by-side and producing the same result.
-# the RForcecom way
-session1 <- RForcecom::rforcecom.login(username, paste0(password, security_token),
- apiVersion=getOption("salesforcer.api_version"))
-session1['sessionID'] <- "{MASKED}"
-session1
-#> sessionID instanceURL
-#> "{MASKED}" "https://na50.salesforce.com/"
-#> apiVersion
-#> "42.0"
-
-# replicated in salesforcer package
-session2 <- salesforcer::rforcecom.login(username, paste0(password, security_token),
- apiVersion=getOption("salesforcer.api_version"))
-session2['sessionID'] <- "{MASKED}"
-session2
-#> sessionID instanceURL
-#> "{MASKED}" "https://na50.salesforce.com/"
-#> apiVersion
-#> "42.0"
# the RForcecom way
+session1 <- RForcecom::rforcecom.login(username, paste0(password, security_token),
+ apiVersion=getOption("salesforcer.api_version"))
+session1['sessionID'] <- "{MASKED}"
+session1
+#> sessionID instanceURL
+#> "{MASKED}" "https://na50.salesforce.com/"
+#> apiVersion
+#> "42.0"
+
+# replicated in salesforcer package
+session2 <- salesforcer::rforcecom.login(username, paste0(password, security_token),
+ apiVersion=getOption("salesforcer.api_version"))
+session2['sessionID'] <- "{MASKED}"
+session2
+#> sessionID instanceURL
+#> "{MASKED}" "https://na50.salesforce.com/"
+#> apiVersion
+#> "42.0"
Note that we must set the API version here because calls to session will not create a new sessionId and then we are stuck with version 35.0 (the default from RForcecom::rforcecom.login). Some functions in salesforcer implement API calls that are only available after version 35.0.
“CRUD” operations (Create, Retrieve, Update, Delete) in the RForcecom package only operate on one record at a time. One benefit to using the salesforcer package is that these operations will accept a named vector (one record) or an entire data.frame
or tbl_df
of records to churn through. However, rest assured that the replicated functions behave exactly the same way if you are hesitant to making the switch.
object <- "Contact"
-fields <- c(FirstName="Test", LastName="Contact-Create-Compatibility")
-
-# the RForcecom way
-result1 <- RForcecom::rforcecom.create(session, objectName=object, fields)
-result1
-#> id success
-#> 1 0036A00000kHtiQQAS true
-
-# replicated in salesforcer package
-result2 <- salesforcer::rforcecom.create(session, objectName=object, fields)
-result2
-#> id success
-#> 1 0036A00000kHtiVQAS true
object <- "Contact"
+fields <- c(FirstName="Test", LastName="Contact-Create-Compatibility")
+
+# the RForcecom way
+result1 <- RForcecom::rforcecom.create(session, objectName=object, fields)
+result1
+#> id success
+#> 1 0036A00000tZzXRQA0 true
+
+# replicated in salesforcer package
+result2 <- salesforcer::rforcecom.create(session, objectName=object, fields)
+result2
+#> id success
+#> 1 0036A00000tZzXWQA0 TRUE
Here is an example showing the reduction in code of using salesforcer if you would like to create multiple records.
-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
- LastName = paste0("Contact-Create-", 1:n))
-
-# the RForcecom way
-rforcecom_results <- NULL
-for(i in 1:nrow(new_contacts)){
- temp <- RForcecom::rforcecom.create(session,
- objectName = "Contact",
- fields = unlist(slice(new_contacts,i)))
- rforcecom_results <- bind_rows(rforcecom_results, temp)
-}
-rforcecom_results
-#> id success
-#> 1 0036A00000kHtiaQAC true
-#> 2 0036A00000kHtifQAC true
-
-# the better way in salesforcer to do multiple records
-salesforcer_results <- sf_create(new_contacts, object_name="Contact")
-salesforcer_results
-#> # A tibble: 2 x 2
-#> id success
-#> <chr> <chr>
-#> 1 0036A00000kHtikQAC true
-#> 2 0036A00000kHtilQAC true
n <- 2
+new_contacts <- tibble(FirstName = rep("Test", n),
+ LastName = paste0("Contact-Create-", 1:n))
+
+# the RForcecom way
+rforcecom_results <- NULL
+for(i in 1:nrow(new_contacts)){
+ temp <- RForcecom::rforcecom.create(session,
+ objectName = "Contact",
+ fields = unlist(slice(new_contacts,i)))
+ rforcecom_results <- bind_rows(rforcecom_results, temp)
+}
+rforcecom_results
+#> id success
+#> 1 0036A00000tZzXbQAK true
+#> 2 0036A00000tZzXgQAK true
+
+# the better way in salesforcer to do multiple records
+salesforcer_results <- sf_create(new_contacts, object_name="Contact")
+salesforcer_results
+#> # A tibble: 2 x 2
+#> id success
+#> <chr> <lgl>
+#> 1 0036A00000tZzXlQAK TRUE
+#> 2 0036A00000tZzXmQAK TRUE
salesforcer also has better printing and type-casting when returning query result thanks to features of the readr package.
-this_soql <- "SELECT Id, Email FROM Contact LIMIT 5"
-
-# the RForcecom way
-result1 <- RForcecom::rforcecom.query(session, soqlQuery = this_soql)
-result1
-#> Id
-#> 1 0036A00000kHn9kQAC
-#> 2 0036A00000kHn9lQAC
-#> 3 0036A00000kHnAYQA0
-#> 4 0036A00000kHnAxQAK
-#> 5 0036A00000kHn4pQAC
-
-# replicated in salesforcer package
-result2 <- salesforcer::rforcecom.query(session, soqlQuery = this_soql)
-result2
-#> # A tibble: 5 x 2
-#> Id Email
-#> * <chr> <lgl>
-#> 1 0036A00000kHn9kQAC NA
-#> 2 0036A00000kHn9lQAC NA
-#> 3 0036A00000kHnAYQA0 NA
-#> 4 0036A00000kHnAxQAK NA
-#> 5 0036A00000kHn4pQAC NA
-
-# the better way in salesforcer to query
-salesforcer_results <- sf_query(this_soql)
-salesforcer_results
-#> # A tibble: 5 x 2
-#> Id Email
-#> * <chr> <lgl>
-#> 1 0036A00000kHn9kQAC NA
-#> 2 0036A00000kHn9lQAC NA
-#> 3 0036A00000kHnAYQA0 NA
-#> 4 0036A00000kHnAxQAK NA
-#> 5 0036A00000kHn4pQAC NA
this_soql <- "SELECT Id, Email FROM Contact LIMIT 5"
+
+# the RForcecom way
+result1 <- RForcecom::rforcecom.query(session, soqlQuery = this_soql)
+result1
+#> Id
+#> 1 0036A00000kHn9kQAC
+#> 2 0036A00000kHn9lQAC
+#> 3 0036A00000kHnAYQA0
+#> 4 0036A00000kHnAxQAK
+#> 5 0036A00000kHtipQAC
+
+# replicated in salesforcer package
+result2 <- salesforcer::rforcecom.query(session, soqlQuery = this_soql)
+result2
+#> # A tibble: 5 x 2
+#> Id Email
+#> <chr> <lgl>
+#> 1 0036A00000kHn9kQAC NA
+#> 2 0036A00000kHn9lQAC NA
+#> 3 0036A00000kHnAYQA0 NA
+#> 4 0036A00000kHnAxQAK NA
+#> 5 0036A00000kHtipQAC NA
+
+# the better way in salesforcer to query
+salesforcer_results <- sf_query(this_soql)
+salesforcer_results
+#> # A tibble: 5 x 2
+#> Id Email
+#> <chr> <lgl>
+#> 1 0036A00000kHn9kQAC NA
+#> 2 0036A00000kHn9lQAC NA
+#> 3 0036A00000kHnAYQA0 NA
+#> 4 0036A00000kHnAxQAK NA
+#> 5 0036A00000kHtipQAC NA
The RForcecom package has the function rforcecom.getObjectDescription()
which returns a data.frame
with one row per field on an object. The same function in salesforcer is named sf_describe_object_fields()
and also has better printing and datatype casting by using tibbles.
# the RForcecom way
-result1 <- RForcecom::rforcecom.getObjectDescription(session, objectName='Account')
-
-# backwards compatible in the salesforcer package
-result2 <- salesforcer::rforcecom.getObjectDescription(session, objectName='Account')
-
-# the better way in salesforcer to get object fields
-result3 <- salesforcer::sf_describe_object_fields('Account')
-result3
-#> # A tibble: 67 x 166
-#> aggregatable autoNumber byteLength calculated caseSensitive createable
-#> <chr> <chr> <int> <chr> <chr> <chr>
-#> 1 true false 18 false false false
-#> 2 false false 0 false false false
-#> 3 true false 18 false false false
-#> 4 true false 765 false false true
-#> 5 true false 120 false false true
-#> 6 true false 18 false false true
-#> 7 true false 765 false false true
-#> 8 true false 120 false false true
-#> 9 true false 240 false false true
-#> 10 true false 60 false false true
-#> # ... with 57 more rows, and 160 more variables: custom <chr>,
-#> # defaultedOnCreate <chr>, deprecatedAndHidden <chr>, digits <int>,
-#> # filterable <chr>, groupable <chr>, idLookup <chr>, label <chr>,
-#> # length <int>, name <chr>, nameField <chr>, namePointing <chr>,
-#> # nillable <chr>, permissionable <chr>, polymorphicForeignKey <chr>,
-#> # precision <int>, queryByDistance <chr>, restrictedPicklist <chr>,
-#> # scale <int>, searchPrefilterable <chr>, soapType <chr>,
-#> # sortable <chr>, type <chr>, unique <chr>, updateable <chr>,
-#> # defaultValue.text <chr>, defaultValue..attrs <chr>, referenceTo <chr>,
-#> # relationshipName <chr>, compoundFieldName <chr>, extraTypeInfo <chr>,
-#> # picklistValues.active <chr>, picklistValues.defaultValue <chr>,
-#> # picklistValues.label <chr>, picklistValues.value <chr>,
-#> # picklistValues.active.1 <chr>, picklistValues.defaultValue.1 <chr>,
-#> # picklistValues.label.1 <chr>, picklistValues.value.1 <chr>,
-#> # picklistValues.active.2 <chr>, picklistValues.defaultValue.2 <chr>,
-#> # picklistValues.label.2 <chr>, picklistValues.value.2 <chr>,
-#> # picklistValues.active.3 <chr>, picklistValues.defaultValue.3 <chr>,
-#> # picklistValues.label.3 <chr>, picklistValues.value.3 <chr>,
-#> # picklistValues.active.4 <chr>, picklistValues.defaultValue.4 <chr>,
-#> # picklistValues.label.4 <chr>, picklistValues.value.4 <chr>,
-#> # picklistValues.active.5 <chr>, picklistValues.defaultValue.5 <chr>,
-#> # picklistValues.label.5 <chr>, picklistValues.value.5 <chr>,
-#> # picklistValues.active.6 <chr>, picklistValues.defaultValue.6 <chr>,
-#> # picklistValues.label.6 <chr>, picklistValues.value.6 <chr>,
-#> # picklistValues.active.7 <chr>, picklistValues.defaultValue.7 <chr>,
-#> # picklistValues.label.7 <chr>, picklistValues.value.7 <chr>,
-#> # picklistValues.active.8 <chr>, picklistValues.defaultValue.8 <chr>,
-#> # picklistValues.label.8 <chr>, picklistValues.value.8 <chr>,
-#> # picklistValues.active.9 <chr>, picklistValues.defaultValue.9 <chr>,
-#> # picklistValues.label.9 <chr>, picklistValues.value.9 <chr>,
-#> # picklistValues.active.10 <chr>, picklistValues.defaultValue.10 <chr>,
-#> # picklistValues.label.10 <chr>, picklistValues.value.10 <chr>,
-#> # picklistValues.active.11 <chr>, picklistValues.defaultValue.11 <chr>,
-#> # picklistValues.label.11 <chr>, picklistValues.value.11 <chr>,
-#> # picklistValues.active.12 <chr>, picklistValues.defaultValue.12 <chr>,
-#> # picklistValues.label.12 <chr>, picklistValues.value.12 <chr>,
-#> # picklistValues.active.13 <chr>, picklistValues.defaultValue.13 <chr>,
-#> # picklistValues.label.13 <chr>, picklistValues.value.13 <chr>,
-#> # picklistValues.active.14 <chr>, picklistValues.defaultValue.14 <chr>,
-#> # picklistValues.label.14 <chr>, picklistValues.value.14 <chr>,
-#> # picklistValues.active.15 <chr>, picklistValues.defaultValue.15 <chr>,
-#> # picklistValues.label.15 <chr>, picklistValues.value.15 <chr>,
-#> # picklistValues.active.16 <chr>, picklistValues.defaultValue.16 <chr>,
-#> # picklistValues.label.16 <chr>, picklistValues.value.16 <chr>,
-#> # picklistValues.active.17 <chr>, …
# the RForcecom way
+result1 <- RForcecom::rforcecom.getObjectDescription(session, objectName='Account')
+
+# backwards compatible in the salesforcer package
+result2 <- salesforcer::rforcecom.getObjectDescription(session, objectName='Account')
+
+# the better way in salesforcer to get object fields
+result3 <- salesforcer::sf_describe_object_fields('Account')
+result3
+#> # A tibble: 67 x 38
+#> aggregatable autoNumber byteLength calculated caseSensitive createable
+#> <chr> <chr> <chr> <chr> <chr> <chr>
+#> 1 true false 18 false false false
+#> 2 false false 0 false false false
+#> 3 true false 18 false false false
+#> 4 true false 765 false false true
+#> 5 true false 120 false false true
+#> 6 true false 18 false false true
+#> 7 true false 765 false false true
+#> 8 true false 120 false false true
+#> 9 true false 240 false false true
+#> 10 true false 60 false false true
+#> # … with 57 more rows, and 32 more variables: custom <chr>,
+#> # defaultedOnCreate <chr>, deprecatedAndHidden <chr>, digits <chr>,
+#> # filterable <chr>, groupable <chr>, idLookup <chr>, label <chr>,
+#> # length <chr>, name <chr>, nameField <chr>, namePointing <chr>,
+#> # nillable <chr>, permissionable <chr>, polymorphicForeignKey <chr>,
+#> # precision <chr>, queryByDistance <chr>, restrictedPicklist <chr>,
+#> # scale <chr>, searchPrefilterable <chr>, soapType <chr>,
+#> # sortable <chr>, type <chr>, unique <chr>, updateable <chr>,
+#> # defaultValue <list>, referenceTo <chr>, relationshipName <chr>,
+#> # compoundFieldName <chr>, extraTypeInfo <chr>, picklistValues <list>,
+#> # externalId <chr>
In the future more features will be migrated from RForcecom to make the transition as seamless as possible.
First, load the salesforcer
package and login.
suppressWarnings(suppressMessages(library(dplyr)))
-library(salesforcer)
-sf_auth()
For really large inserts, updates, deletes, upserts, queries you can just add “api_type” = “Bulk” to most functions to get the benefits of using the Bulk API instead of the SOAP or REST APIs. Here is the difference in using the REST API vs. the Bulk API to do an insert:
-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
- LastName = paste0("Contact-Create-", 1:n))
-# REST
-rest_created_records <- sf_create(new_contacts, object_name="Contact", api_type="REST")
-rest_created_records
-#> # A tibble: 2 x 3
-#> id success errors
-#> <chr> <lgl> <list>
-#> 1 0036A00000kHtipQAC TRUE <list [0]>
-#> 2 0036A00000kHtiqQAC TRUE <list [0]>
-# Bulk
-bulk_created_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
-bulk_created_records
-#> # A tibble: 2 x 4
-#> Id Success Created Error
-#> <chr> <chr> <chr> <chr>
-#> 1 0036A00000kHtiuQAC true true <NA>
-#> 2 0036A00000kHtivQAC true true <NA>
For really large inserts, updates, deletes, upserts, queries you can just add “api_type” = “Bulk” to most functions to get the benefits of using the Bulk API instead of the SOAP or REST APIs. Here is the difference in using the REST API vs. the Bulk API to do an insert:
+n <- 2
+new_contacts <- tibble(FirstName = rep("Test", n),
+ LastName = paste0("Contact-Create-", 1:n))
+# REST
+rest_created_records <- sf_create(new_contacts, object_name="Contact", api_type="REST")
+rest_created_records
+#> # A tibble: 2 x 3
+#> id success errors
+#> <chr> <lgl> <list>
+#> 1 0036A00000tZzXqQAK TRUE <list [0]>
+#> 2 0036A00000tZzXrQAK TRUE <list [0]>
+# Bulk
+bulk_created_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
+bulk_created_records
+#> # A tibble: 2 x 4
+#> Id Success Created Error
+#> <chr> <lgl> <lgl> <lgl>
+#> 1 0036A00000tZzXhQAK TRUE TRUE NA
+#> 2 0036A00000tZzXiQAK TRUE TRUE NA
There are some differences in the way each API returns response information; however, the end result is exactly the same for these two calls. Also, note that this package utilizes the Bulk 2.0 API for most bulk calls except for bulk queries since Salesforce has not yet implemented it in 2.0.
Here is a simple workflow of adding, querying, and deleting records using the Bulk 1.0 API.
-# just add api_type="Bulk 1.0" or api_type="Bulk 2.0" to most calls!
-# create bulk
-object <- "Contact"
-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
- LastName = paste0("Contact-Create-", 1:n))
-created_records <- sf_create(new_contacts, object_name=object, api_type="Bulk 1.0")
-created_records
-#> # A tibble: 2 x 4
-#> Id Success Created Error
-#> <chr> <chr> <chr> <chr>
-#> 1 0036A00000kHtj9QAC true true <NA>
-#> 2 0036A00000kHtjAQAS true true <NA>
-
-# query bulk
-my_soql <- sprintf("SELECT Id,
- FirstName,
- LastName
- FROM Contact
- WHERE Id in ('%s')",
- paste0(created_records$Id , collapse="','"))
-
-queried_records <- sf_query(my_soql, object_name=object, api_type="Bulk 1.0")
-queried_records
-#> # A tibble: 2 x 3
-#> Id FirstName LastName
-#> <chr> <chr> <chr>
-#> 1 0036A00000kHtj9QAC Test Contact-Create-1
-#> 2 0036A00000kHtjAQAS Test Contact-Create-2
-
-# delete bulk
-deleted_records <- sf_delete(queried_records$Id, object_name=object, api_type="Bulk 1.0")
-deleted_records
-#> # A tibble: 2 x 4
-#> Id Success Created Error
-#> <chr> <chr> <chr> <chr>
-#> 1 0036A00000kHtj9QAC true false <NA>
-#> 2 0036A00000kHtjAQAS true false <NA>
# just add api_type="Bulk 1.0" or api_type="Bulk 2.0" to most calls!
+# create bulk
+object <- "Contact"
+n <- 2
+new_contacts <- tibble(FirstName = rep("Test", n),
+ LastName = paste0("Contact-Create-", 1:n))
+created_records <- sf_create(new_contacts, object_name=object, api_type="Bulk 1.0")
+created_records
+#> # A tibble: 2 x 4
+#> Id Success Created Error
+#> <chr> <lgl> <lgl> <lgl>
+#> 1 0036A00000tZzY0QAK TRUE TRUE NA
+#> 2 0036A00000tZzY1QAK TRUE TRUE NA
+
+# query bulk
+my_soql <- sprintf("SELECT Id,
+ FirstName,
+ LastName
+ FROM Contact
+ WHERE Id in ('%s')",
+ paste0(created_records$Id , collapse="','"))
+
+queried_records <- sf_query(my_soql, object_name=object, api_type="Bulk 1.0")
+queried_records
+#> # A tibble: 2 x 3
+#> Id FirstName LastName
+#> <chr> <chr> <chr>
+#> 1 0036A00000tZzY0QAK Test Contact-Create-1
+#> 2 0036A00000tZzY1QAK Test Contact-Create-2
+
+# delete bulk
+deleted_records <- sf_delete(queried_records$Id, object_name=object, api_type="Bulk 1.0")
+deleted_records
+#> # A tibble: 2 x 4
+#> Id Success Created Error
+#> <chr> <lgl> <lgl> <lgl>
+#> 1 0036A00000tZzY0QAK TRUE FALSE NA
+#> 2 0036A00000tZzY1QAK TRUE FALSE NA
# install from CRAN
-install.packages("salesforcer")
-
-# or get the latest version available on GitHub using the devtools package
-# install.packages("devtools")
-devtools::install_github("StevenMMortimer/salesforcer")
# install from CRAN
+install.packages("salesforcer")
+
+# or get the latest version available on GitHub using the devtools package
+# install.packages("devtools")
+devtools::install_github("StevenMMortimer/salesforcer")
If you encounter a clear bug, please file a minimal reproducible example on GitHub.
It is recommended to use OAuth 2.0 so that passwords do not have to be shared or embedded within scripts. User credentials will be stored in locally cached file entitled “.httr-oauth-salesforcer” in the current working directory.
-suppressWarnings(suppressMessages(library(dplyr)))
-suppressWarnings(suppressMessages(library(purrr)))
-library(salesforcer)
-
-# Using OAuth 2.0 authentication
-sf_auth()
-
-# Using Basic Username-Password authentication
-sf_auth(username = "test@gmail.com",
- password = "{PASSWORD_HERE}",
- security_token = "{SECURITY_TOKEN_HERE}")
suppressWarnings(suppressMessages(library(dplyr)))
+suppressWarnings(suppressMessages(library(purrr)))
+library(salesforcer)
+
+# Using OAuth 2.0 authentication
+sf_auth()
+
+# Using Basic Username-Password authentication
+sf_auth(username = "test@gmail.com",
+ password = "{PASSWORD_HERE}",
+ security_token = "{SECURITY_TOKEN_HERE}")
After logging in with sf_auth()
, you can check your connectivity by looking at the information returned about the current user. It should be information about you!
# pull down information of person logged in
-# it's a simple easy call to get started
-# and confirm a connection to the APIs
-user_info <- sf_user_info()
-sprintf("User Id: %s", user_info$id)
-#> character(0)
-sprintf("User Active?: %s", user_info$isActive)
-#> character(0)
Salesforce has objects and those objects contain records. One default object is the “Contact” object. This example shows how to create two records in the Contact object.
-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
- LastName = paste0("Contact-Create-", 1:n))
-created_records <- sf_create(new_contacts, object_name="Contact")
-created_records
-#> # A tibble: 2 x 2
-#> id success
-#> <chr> <chr>
-#> 1 0036A00000kHth8QAC true
-#> 2 0036A00000kHth9QAC true
n <- 2
+new_contacts <- tibble(FirstName = rep("Test", n),
+ LastName = paste0("Contact-Create-", 1:n))
+created_records <- sf_create(new_contacts, object_name="Contact")
+created_records
+#> # A tibble: 2 x 2
+#> id success
+#> <chr> <lgl>
+#> 1 0036A00000tZzToQAK TRUE
+#> 2 0036A00000tZzTpQAK TRUE
Salesforce has proprietary form of SQL called SOQL (Salesforce Object Query Language). SOQL is a powerful tool that allows you to return the attributes of records on almost any object in Salesforce including Accounts, Contacts, Tasks, Opportunities, even Attachments! Below is an example where we grab the data we just created including Account object information for which the Contact record is associated with. The Account column is all NA
since we have yet to provide information to link these Contacts with Accounts.
my_soql <- sprintf("SELECT Id,
- Account.Name,
- FirstName,
- LastName
- FROM Contact
- WHERE Id in ('%s')",
- paste0(created_records$id , collapse="','"))
-
-queried_records <- sf_query(my_soql)
-queried_records
-#> # A tibble: 2 x 4
-#> Id Account FirstName LastName
-#> * <chr> <lgl> <chr> <chr>
-#> 1 0036A00000kHth8QAC NA Test Contact-Create-1
-#> 2 0036A00000kHth9QAC NA Test Contact-Create-2
my_soql <- sprintf("SELECT Id,
+ Account.Name,
+ FirstName,
+ LastName
+ FROM Contact
+ WHERE Id in ('%s')",
+ paste0(created_records$id , collapse="','"))
+
+queried_records <- sf_query(my_soql)
+queried_records
+#> # A tibble: 2 x 4
+#> Id Account FirstName LastName
+#> <chr> <lgl> <chr> <chr>
+#> 1 0036A00000tZzToQAK NA Test Contact-Create-1
+#> 2 0036A00000tZzTpQAK NA Test Contact-Create-2
After creating records you can update them using sf_update()
. Updating a record requires you to pass the Salesforce Id
of the record. Salesforce creates a unique 18-character identifier on each record and uses that to know which record to attach the update information you provide. Simply include a field or column in your update dataset called “Id” and the information will be matched. Here is an example where we update each of the records we created earlier with a new first name called “TestTest”.
# Update some of those records
-queried_records <- queried_records %>%
- mutate(FirstName = "TestTest") %>%
- select(-Account)
-
-updated_records <- sf_update(queried_records, object_name="Contact")
-updated_records
-#> # A tibble: 2 x 2
-#> id success
-#> <chr> <chr>
-#> 1 0036A00000kHth8QAC true
-#> 2 0036A00000kHth9QAC true
# Update some of those records
+queried_records <- queried_records %>%
+ mutate(FirstName = "TestTest") %>%
+ select(-Account)
+
+updated_records <- sf_update(queried_records, object_name="Contact")
+updated_records
+#> # A tibble: 2 x 2
+#> id success
+#> <chr> <lgl>
+#> 1 0036A00000tZzToQAK TRUE
+#> 2 0036A00000tZzTpQAK TRUE
For really large operations (inserts, updates, upserts, deletes, and queries) Salesforce provides the Bulk 1.0 and Bulk 2.0 APIs. In order to use the Bulk APIs in salesforcer you can just add api_type = "Bulk 1.0"
or api_type = "Bulk 2.0"
to your functions and the operation will be executed using the Bulk APIs. It’s that simple.
The benefits of using the Bulk API for larger datasets is that the operation will reduce the number of individual API calls (organization usually have a limit on total calls) and batching the requests in bulk is usually quicker than running thousands of individuals calls when your data is large. Note: the Bulk 2.0 API does NOT guarantee the order of the data submitted is preserved in the output. This means that you must join on other data columns to match up the Ids that are returned in the output with the data you submitted. For this reason, Bulk 2.0 may not be a good solution for creating, updating, or upserting records where you need to keep track of the created Ids. The Bulk 2.0 API would be fine for deleting records where you only need to know which Ids were successfully deleted.
-# create contacts using the Bulk API
-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
- LastName = paste0("Contact-Create-", 1:n))
-created_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
-
-# query large recordsets using the Bulk API
-my_soql <- sprintf("SELECT Id,
- FirstName,
- LastName
- FROM Contact
- WHERE Id in ('%s')",
- paste0(created_records$Id , collapse="','"))
-
-queried_records <- sf_query(my_soql, object_name="Contact", api_type="Bulk 1.0")
-
-# delete these records using the Bulk API
-deleted_records <- sf_delete(queried_records$Id, object_name="Contact", api_type="Bulk 1.0")
# create contacts using the Bulk API
+n <- 2
+new_contacts <- tibble(FirstName = rep("Test", n),
+ LastName = paste0("Contact-Create-", 1:n))
+created_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
+
+# query large recordsets using the Bulk API
+my_soql <- sprintf("SELECT Id,
+ FirstName,
+ LastName
+ FROM Contact
+ WHERE Id in ('%s')",
+ paste0(created_records$Id , collapse="','"))
+
+queried_records <- sf_query(my_soql, object_name="Contact", api_type="Bulk 1.0")
+
+# delete these records using the Bulk API
+deleted_records <- sf_delete(queried_records$Id, object_name="Contact", api_type="Bulk 1.0")
Salesforce is a very flexible platform. Salesforce provides the Metadata API for users to create, read, update and delete the objects, page layouts, and more. This makes it very easy to programmatically setup and teardown the Salesforce environment. One common use case for the Metadata API is retrieving information about an object (fields, permissions, etc.). You can use the sf_read_metadata()
function to return a list of objects and their metadata. In the example below we retrieve the metadata for the Account and Contact objects. Note that the metadata_type
argument is “CustomObject”. Standard Objects are an implementation of CustomObjects, so they are returned using that metadata type.
read_obj_result <- sf_read_metadata(metadata_type='CustomObject',
- object_names=c('Account', 'Contact'))
-read_obj_result[[1]][c('fullName', 'label', 'sharingModel', 'enableHistory')]
-#> $fullName
-#> [1] "Account"
-#>
-#> $label
-#> [1] "Account"
-#>
-#> $sharingModel
-#> [1] "ReadWrite"
-#>
-#> $enableHistory
-#> [1] "false"
-first_two_fields_idx <- head(which(names(read_obj_result[[1]]) == 'fields'), 2)
-# show the first two returned fields of the Account object
-read_obj_result[[1]][first_two_fields_idx]
-#> $fields
-#> $fields$fullName
-#> [1] "AccountNumber"
-#>
-#> $fields$trackFeedHistory
-#> [1] "false"
-#>
-#>
-#> $fields
-#> $fields$fullName
-#> [1] "AccountSource"
-#>
-#> $fields$trackFeedHistory
-#> [1] "false"
-#>
-#> $fields$type
-#> [1] "Picklist"
read_obj_result <- sf_read_metadata(metadata_type='CustomObject',
+ object_names=c('Account', 'Contact'))
+read_obj_result[[1]][c('fullName', 'label', 'sharingModel', 'enableHistory')]
+#> $fullName
+#> [1] "Account"
+#>
+#> $label
+#> [1] "Account"
+#>
+#> $sharingModel
+#> [1] "ReadWrite"
+#>
+#> $enableHistory
+#> [1] "false"
+first_two_fields_idx <- head(which(names(read_obj_result[[1]]) == 'fields'), 2)
+# show the first two returned fields of the Account object
+read_obj_result[[1]][first_two_fields_idx]
+#> $fields
+#> $fields$fullName
+#> [1] "AccountNumber"
+#>
+#> $fields$trackFeedHistory
+#> [1] "false"
+#>
+#>
+#> $fields
+#> $fields$fullName
+#> [1] "AccountSource"
+#>
+#> $fields$trackFeedHistory
+#> [1] "false"
+#>
+#> $fields$type
+#> [1] "Picklist"
The data is returned as a list because object definitions are highly nested representations. You may notice that we are missing some really specific details, such as, the picklist values of a field with type “Picklist”. You can get that information using the sf_describe_object_fields()
or sf_describe_objects()
functions which are based on calls from REST and SOAP APIs. Here is an example using sf_describe_object_fields()
where we get a tbl_df
with one row for each field on the Account object:
acct_fields <- sf_describe_object_fields('Account')
-acct_fields %>% select(name, label, length, soapType, type)
-#> # A tibble: 67 x 5
-#> name label length soapType type
-#> <chr> <chr> <int> <chr> <chr>
-#> 1 Id Account ID 18 tns:ID id
-#> 2 IsDeleted Deleted 0 xsd:boolean boolean
-#> 3 MasterRecordId Master Record ID 18 tns:ID reference
-#> 4 Name Account Name 255 xsd:string string
-#> 5 Type Account Type 40 xsd:string picklist
-#> 6 ParentId Parent Account ID 18 tns:ID reference
-#> 7 BillingStreet Billing Street 255 xsd:string textarea
-#> 8 BillingCity Billing City 40 xsd:string string
-#> 9 BillingState Billing State/Province 80 xsd:string string
-#> 10 BillingPostalCode Billing Zip/Postal Code 20 xsd:string string
-#> # ... with 57 more rows
acct_fields <- sf_describe_object_fields('Account')
+acct_fields %>% select(name, label, length, soapType, type)
+#> # A tibble: 67 x 5
+#> name label length soapType type
+#> <chr> <chr> <chr> <chr> <chr>
+#> 1 Id Account ID 18 tns:ID id
+#> 2 IsDeleted Deleted 0 xsd:boolean boolean
+#> 3 MasterRecordId Master Record ID 18 tns:ID reference
+#> 4 Name Account Name 255 xsd:string string
+#> 5 Type Account Type 40 xsd:string picklist
+#> 6 ParentId Parent Account ID 18 tns:ID reference
+#> 7 BillingStreet Billing Street 255 xsd:string textarea
+#> 8 BillingCity Billing City 40 xsd:string string
+#> 9 BillingState Billing State/Province 80 xsd:string string
+#> 10 BillingPostalCode Billing Zip/Postal Code 20 xsd:string string
+#> # … with 57 more rows
If you prefer to be more precise about collecting and formatting the field data you can work directly with the nested lists that the APIs return. In this example we look at the picklist values of fields on the Account object.
-describe_obj_result <- sf_describe_objects(object_names=c('Account', 'Contact'))
-# confirm that the Account object is queryable
-describe_obj_result[[1]][c('label', 'queryable')]
-#> $label
-#> [1] "Account"
-#>
-#> $queryable
-#> [1] "true"
-# show the different picklist values for the Account Type field
-the_type_field <- describe_obj_result[[1]][[59]]
-the_type_field$label
-#> NULL
-map_df(the_type_field[which(names(the_type_field) == "picklistValues")], as_tibble)
-#> # A tibble: 0 x 0
describe_obj_result <- sf_describe_objects(object_names=c('Account', 'Contact'))
+# confirm that the Account object is queryable
+describe_obj_result[[1]][c('label', 'queryable')]
+#> $label
+#> [1] "Account"
+#>
+#> $queryable
+#> [1] "true"
+# show the different picklist values for the Account Type field
+the_type_field <- describe_obj_result[[1]][[59]]
+the_type_field$label
+#> NULL
+map_df(the_type_field[which(names(the_type_field) == "picklistValues")], as_tibble)
+#> # A tibble: 0 x 0
It is recommended that you try out the various metadata functions sf_read_metadata()
, sf_list_metadata()
, sf_describe_metadata()
, and sf_describe_objects()
in order to see which information best suits your use case.
Where the Metadata API really shines is when it comes to CRUD operations on metadata. In this example we will create an object, add fields to it, then delete that object.
-# create an object
-base_obj_name <- "Custom_Account1"
-custom_object <- list()
-custom_object$fullName <- paste0(base_obj_name, "__c")
-custom_object$label <- paste0(gsub("_", " ", base_obj_name))
-custom_object$pluralLabel <- paste0(base_obj_name, "s")
-custom_object$nameField <- list(displayFormat = 'AN-{0000}',
- label = paste0(base_obj_name, ' Number'),
- type = 'AutoNumber')
-custom_object$deploymentStatus <- 'Deployed'
-custom_object$sharingModel <- 'ReadWrite'
-custom_object$enableActivities <- 'true'
-custom_object$description <- paste0(base_obj_name, " created by the Metadata API")
-custom_object_result <- sf_create_metadata(metadata_type = 'CustomObject',
- metadata = custom_object)
-
-# add fields to the object
-custom_fields <- tibble(fullName=c(paste0(base_obj_name, '__c.CustomField3__c'),
- paste0(base_obj_name, '__c.CustomField4__c')),
- label=c('Test Field3', 'Test Field4'),
- length=c(100, 100),
- type=c('Text', 'Text'))
-create_fields_result <- sf_create_metadata(metadata_type = 'CustomField',
- metadata = custom_fields)
-
-# delete the object
-deleted_custom_object_result <- sf_delete_metadata(metadata_type = 'CustomObject',
- object_names = c('Custom_Account1__c'))
# create an object
+base_obj_name <- "Custom_Account1"
+custom_object <- list()
+custom_object$fullName <- paste0(base_obj_name, "__c")
+custom_object$label <- paste0(gsub("_", " ", base_obj_name))
+custom_object$pluralLabel <- paste0(base_obj_name, "s")
+custom_object$nameField <- list(displayFormat = 'AN-{0000}',
+ label = paste0(base_obj_name, ' Number'),
+ type = 'AutoNumber')
+custom_object$deploymentStatus <- 'Deployed'
+custom_object$sharingModel <- 'ReadWrite'
+custom_object$enableActivities <- 'true'
+custom_object$description <- paste0(base_obj_name, " created by the Metadata API")
+custom_object_result <- sf_create_metadata(metadata_type = 'CustomObject',
+ metadata = custom_object)
+
+# add fields to the object
+custom_fields <- tibble(fullName=c(paste0(base_obj_name, '__c.CustomField3__c'),
+ paste0(base_obj_name, '__c.CustomField4__c')),
+ label=c('Test Field3', 'Test Field4'),
+ length=c(100, 100),
+ type=c('Text', 'Text'))
+create_fields_result <- sf_create_metadata(metadata_type = 'CustomField',
+ metadata = custom_fields)
+
+# delete the object
+deleted_custom_object_result <- sf_delete_metadata(metadata_type = 'CustomObject',
+ object_names = c('Custom_Account1__c'))
NEWS.md
sf_describe_object_fields()
which is a tidyier version of rforcecom.getObjectDescription()
sf_get_all_jobs_bulk()
so that users can see retrieve details for all bulk jobs (#13)sf_set_password()
and sf_reset_password()
(#11)sf_find_duplicates()
, sf_find_duplicates_by_id()
) (#4)sf_describe_object_fields()
more robust against nested list elements (#16)build_soap_xml_from_list(input_data, operation = c("create", "retrieve", "update", "upsert", "delete", "search", "query", "queryMore", - "describeSObjects", "setPassword", "resetPassword"), - object_name = NULL, fields = NULL, external_id_fieldname = NULL, - root_name = NULL, ns = c(character(0)), root = NULL)+ "describeSObjects", "setPassword", "resetPassword", "findDuplicates", + "findDuplicatesByIds"), object_name = NULL, fields = NULL, + external_id_fieldname = NULL, root_name = NULL, + ns = c(character(0)), root = NULL)
x | +list; a list, typically returned from the API that we would parse through |
+
---|
A list
containing one row per field for the requested object.
The tibble only contains the fields that the user can view, as defined by +the user's field-level security settings.
+ + +# NOT RUN { +obj_dat <- sf_describe_objects(object_names = object_name, api_type = "SOAP")[[1]] +obj_fields_list <- obj_dat[names(obj_dat) == "fields"] %>% + map(collapse_list_with_dupe_names) +# }+
Performs rule-based searches for duplicate records.
+ +sf_find_duplicates(search_criteria, object_name, + include_record_details = FALSE, verbose = FALSE)+ +
search_criteria | +
|
+
---|---|
object_name | +character; the name of one Salesforce objects that the +function is operating against (e.g. "Account", "Contact", "CustomObject__c") |
+
include_record_details | +logical; get fields and values for records detected
+as duplicates by setting this property to |
+
verbose | +logical; do you want informative messages? |
+
tbl_df
of records found to be duplicates by the match rules
You must have actived duplicate rules for the supplied object before running
+this function. The object_name
argument refers to using that object's duplicate
+rules on the search criteria to determine which records in other objects are duplicates.
# NOT RUN { +# if insert a lead with this email address, what duplicate records exist elsewhere +# according to the Lead object's duplicate rules +found_dupes <- sf_find_duplicates(search_criteria = list(Email = "bond_john@grandhotels.com"), + object_name = "Lead") + +# now look across all other objects using the Contact object rules +found_dupes <- sf_find_duplicates(search_criteria = list(Email = "bond_john@grandhotels.com"), + object_name = "Contact") +# }+
Performs rule-based searches for duplicate records.
+ +sf_find_duplicates_by_id(sf_id, include_record_details = FALSE, + verbose = FALSE)+ +
sf_id | +character; a Salesforce generated Id that identifies a record |
+
---|---|
include_record_details | +logical; get fields and values for records detected
+as duplicates by setting this property to |
+
verbose | +logical; do you want informative messages? |
+
tbl_df
of records found to be duplicates by the match rules
You must have actived duplicate rules for the supplied object before running +this function. This function uses the duplicate rules for the object that has +the same type as the input record IDs. For example, if the record Id represents +an Account, this function uses the duplicate rules associated with the +Account object.
+ + +# NOT RUN { +found_dupes <- sf_find_duplicates_by_id(sf_id = "00Q6A00000aABCnZZZ") +# }+
sf_query(soql, object_name, queryall = FALSE, page_size = 1000, - api_type = c("REST", "SOAP", "Bulk 1.0"), next_records_url = NULL, - ..., verbose = FALSE)+
sf_query(soql, object_name, guess_types = TRUE, queryall = FALSE, + page_size = 1000, api_type = c("REST", "SOAP", "Bulk 1.0"), + next_records_url = NULL, ..., verbose = FALSE)