Exporting Cards to an External System
Publish card content to an external system with the REST API or the Python SDK's Publisher framework.
A common use case is publishing card content from Guru to an external system, such as a customer-facing help center or documentation site. There are two approaches, and this guide covers both:
| Approach | Best for |
|---|---|
| REST API | Any language or platform. You run the exports, track changes, and handle create-versus-update in the destination yourself. |
Python SDK Publisher | Python integrations. The SDK enumerates your content, tracks published versions, remembers external IDs, and decides create versus update for you. |
Export with the REST API
The pattern is a one-time full export with the List Cards endpoint, followed by periodic filtered exports that capture only what changed.
Full export
First, do an initial export with a wide-open query:
curl -u $GURU_USER:$GURU_TOKEN "https://api.getguru.com/api/v1/search/query"This returns an array of all card objects. In most accounts, the results are paged; see Pagination for how to process all results.
Filtered export
After the full export, capture changes using the card's lastModified field. Suppose the initial export was done on January 15th, 2016 at 8am UTC, which is 2016-01-15T08:00:00.000+00:00 in ISO-8601 format. For the first sync of changes, use a lastModified filter (URL encode the query in practice):
curl -u $GURU_USER:$GURU_TOKEN "https://api.getguru.com/api/v1/search/query?q=lastModified >= 2016-01-15T08:00:00.000+00:00"This returns all cards modified since that time; again, results may be paged. Record the date and time of each sync and use it in the next query. For example, if the last sync ran on January 16th at 10am UTC, the next query would filter on lastModified >= 2016-01-16T10:00:00.000+00:00.
Periodic exports like this ensure all changes reach your destination.
Avoid creating duplicatesThe filtered export returns existing card IDs. It's up to your destination system to overwrite its copy of that data; otherwise you'll create duplicate records.
Publish with the Python SDK
The Python SDK provides a Publisher framework that handles the Guru side of this job for you. This walkthrough uses an Intercom Help Center as the example destination.
Legacy board namingThe SDK predates Guru's transition from boards to folders, so its classes and methods refer to "boards" and "board groups." These structures appear as folders in today's Guru.
Understand the nomenclature
The SDK refers to objects by their Guru names (cards, boards, and so on) and refers to the third party as "external." When we publish a Guru card to become an Intercom article, the SDK calls the Intercom article an "external card."
Intercom also has grouping structures called collections and sections, which map to Guru objects like this:
| Guru name | SDK generic name | Intercom name |
|---|---|---|
| Collection | external collection | Collection |
| Folder | external section | Section |
| Card | external card | Article |
Run the script
The script needs three environment variables:
GURU_USER: your Guru username (email address).GURU_API_TOKEN: your Guru API token.INTERCOM_API_TOKEN: your Intercom API token.
With those set, run the intercom_publish.py script:
GURU_USER="[email protected]" GURU_API_TOKEN="$GURU_TOKEN" \
INTERCOM_API_TOKEN="$INTERCOM_TOKEN" \
python intercom_publish.pyThe Publisher class
Publisher classThe SDK provides a base Publisher class that implements everything you need on the Guru side. It:
- Enumerates the board groups, boards, sections, and cards in a collection.
- Tracks object versions so it knows when a card has changed and needs to be published.
- Remembers the external ID of each object.
- Knows whether an object has been published before, so it knows when to create versus update the external object.
- Finds links between Guru cards and helps you convert them to links between external articles.
To publish cards to Intercom, we extend the SDK's Publisher class to create an IntercomPublisher class. That subclass is where we implement what's specific to Intercom, like the API calls to create and update Intercom articles.
To publish some content, we just tell it where to start:
import guru
g = guru.Guru()
publisher = IntercomPublisher(g)
publisher.publish_collection("Help Center")This tells the SDK to enumerate all of the boards, sections, and cards in your Help Center collection and see which objects have changes that need to be published. When it finds an object that needs to be created or updated in Intercom, it calls the method that makes that update. Those methods are Intercom-specific, so they're the ones we implement in IntercomPublisher.
Create and update Intercom articles
When a card needs to be published, one of two methods is called:
create_external_cardwhen the card has never been published and needs to be created in Intercom.update_external_cardwhen the card has been published before and the Intercom article needs updating.
Here's how we implement them:
def create_external_card(self, card, changes, section, board, board_group, collection):
data = self.convert_card_to_article(card, section, board)
url = "https://api.intercom.io/articles"
# we return the Intercom article's ID.
return requests.post(url, json=data, headers=self.get_headers()).json().get("id")
def update_external_card(self, external_id, card, changes, section, board, board_group, collection):
data = self.convert_card_to_article(card, section, board)
url = "https://api.intercom.io/articles/%s" % external_id
# this method returns the response object so the SDK will know
# if the API call to update the article was successful.
return requests.put(url, json=data, headers=self.get_headers())One makes the POST call to create an Intercom article; the other makes the PUT call to update it.
When a new card is created, the SDK calls create_external_card. We return the Intercom article's ID, and the SDK remembers it. When that card is later updated in Guru, the SDK calls update_external_card and provides the Intercom article ID as the external_id parameter.
To format the JSON payload for these calls, we define a convert_card_to_article method that converts a Guru card object to the format Intercom expects:
def convert_card_to_article(self, card, section, board):
data = {
"title": card.title,
"author_id": 5056532,
"body": card.content,
"state": "published",
}
# if the card is on a section, that's its parent in Intercom.
# if it's on a board but not a section, the board is its parent in Intercom.
if section:
data["parent_id"] = self.get_external_id(section.id)
data["parent_type"] = "section"
elif board:
data["parent_id"] = self.get_external_id(board.id)
data["parent_type"] = "collection"
return dataFind Intercom collections and sections
There are two ways the SDK can be aware of an external object:
- It created the object and remembers its external ID.
- It checks for an existing object and finds one whose name matches.
The sample script does not create or update Intercom collections or sections; it expects them to already exist. When the SDK sees a board in Guru and doesn't know its Intercom ID, it calls find_external_board, which lists Intercom collections and checks whether any name matches:
def find_external_board(self, guru_board):
intercom_collections = self.get_all("https://api.intercom.io/help_center/collections")
for intercom_collection in intercom_collections:
if intercom_collection["name"].lower() == guru_board.title.lower():
return intercom_collection["id"]When we create an article, its parent_id must be the Intercom ID of the collection or section, and this is how we know that ID.
How the script stores data
The script needs to remember information from one run to the next:
- Which cards have been published.
- Which version of each card was published.
- The Intercom article ID that corresponds to each Guru card.
- The Intercom IDs for other objects.
All of this metadata is stored in a JSON file called IntercomPublisher.json, written to the script's working directory.
Updated 17 days ago

