ChairNerd

Code, Design & Growth at SeatGeek

Jobs at SeatGeek

We currently have more than 20 open positions.

Visit our Jobs page

SeatGeek Marketplace

We started SeatGeek out of personal frustration. Buying tickets was so consumer-antagonistic that we went to far fewer events than we’d like. Today, millions of fans shop on SeatGeek. We hope that by creating a simple, transparent way to buy tickets we’ve empowered people who would have stayed home to instead get out and see a show or game [1].

Today we’re taking a big, exciting leap towards further democratizing live entertainment: we’re introducing SeatGeek Marketplace, a new way to sell tickets. As a component of Marketplace, we’re also launching peer-to-peer ticket transfer and sales.

Selling on SeatGeek

We set out to create a selling experience as easy as the SeatGeek buying experience. The status quo for mobile ticket selling is a pain; as a result, fans often throw out unused tickets or simply don’t buy them in the first place [2]. For years, we’ve been getting emails from users asking how they can list their tickets on SeatGeek. That feature has been requested more than all other features combined.

Selling on SeatGeek is dead simple – upload your tickets and we take care of the rest. After uploading, we automatically parse the file for all relevant details, such as the event, section, row, etc. We use our valuation engine – the same one that powers Deal Score – to recommend a price that maximizes your expected earnings.

Suppose you bought tickets to see the Raptors play the Clippers this weekend, but your spouse is sick, so the two of you decide to stay home and catch up on Netflix. The money you paid for those tickets is not lost! In 17 seconds you can have them listed on SeatGeek [3].

Sharing & Peer-to-Peer Sales

Rather than selling your Clippers tickets on the Marketplace, suppose you want to give them to a friend. The SeatGeek app now allows you to send tickets to anyone else, right from your phone. Your friend will be able to get into the game with the app – no printing, no meeting in front of the venue.

You also have the option to request money as part of your ticket transfer. In these cases, the recipient won’t be able to access the ticket until they pay you (protection against that friend who keeps telling you she’ll “pay you soon” for a dinner bill from four months ago). Even though money is changing hands, SeatGeek charges no fee for these transfers. You can get the money paid into your bank account or sent to you on Venmo.

Try It Out

Sharing and Peer-to-Peer Sales are available today on our website and iOS app. Marketplace is available on our website today and will be available on iOS & mobile web within the next few days. We are busily working on adding these features to our Android app, and will be rolling out Android support over the next few months.

Get Out There

We believe that experiences – more than physical things – bring happiness to people’s lives; our mission at SeatGeek is to build software that enables that happiness.

We created Marketplace to empower you to have more fun. Getting to a game or show doesn’t have to be a pain, and yet often still is. We hope that by creating a simple, liquid marketplace for tickets, live entertainment becomes a bit more accessible to everyone. We hope you love it.

Notes

[1] Venues and teams we’ve partnered with tell us that 80% of the users who bought tickets on SeatGeek were completely new to their arena.

[2] By way of analogy, suppose there were no way to resell or give away an iPhone – when you bought a new phone, you simply had to discard the old one. People’s willingness to pay for new iPhones would decrease. I know I’m far more willing to buy products online when it’s free and easy to return whatever I can’t use.

[3] The 17 second datapoint comes from this video, but single digit times should be attainable! Standard marketplace disclaimers apply here: we can’t guarantee your tickets will sell. The ticket resale market is dynamic, so prices may have moved down since your purchase, meaning you only get back a fraction of what you paid (on the other hand, prices may have increased, giving you some extra spending money).

Solving Address Entry on Android: PlacesAutocompleteTextView

An address-autocompleting text field for Android

When we started building our mobile checkout experience on Android, we wanted to ensure our mobile data entry experience was as smooth as possible. At its core, checkout is a painful experience riddled with text entry fields, lost data and obscure errors. There have been many great posts about optimizing the checkout experience for mobile that we drew inspiration from, each with their own set of best practices, tips and tricks. We tried our best to distill that pool of information into a few key user stories to focus on:

  1. I do not want to enter data that you should be able to infer

  2. I do not want to type the same information more than once

  3. You should already know where I am when I need to enter my address

Using recommendations, best practices and imitating the best checkout forms we saw, we built what we think is a great component to any checkout form: an address autocompletion view. Today, we’re making it open source: PlacesAutocompleteTextView

Text entry on mobile

Mobile devices have many strategies for simplifying text entry; autocorrection, input suggestions, and swipeable keyboards all do wonders to simplify the user experience. Despite all that, it remains true that physically typing text is challenging: digital keyboards generally have smaller keys, users do not hold the phone in both hands all the time and autocorrect can only correct so well.

The text entry problem is then exacerbated by needing to type postal addresses for billing and shipping information. Addresses often switch between text and numeric input, have pronouns not in autocorrect dictionaries and are often broken up into several different input fields that the user has to navigate.

We looked at how other sites and apps were limiting the amount of data that a user needed to enter to complete a checkout form and saw that an autocompleting address entry field was far above the rest in terms of reducing keystrokes.

PlacesAutocompleteTextView

PlacesAutocompleteTextView is an extension of the Android framework AutocompleteTextView that interacts with the Google Maps Places Autocomplete API to suggest addresses as soon as a user starts typing. Here’s a demo of the view in action in the checkout form of the SeatGeek for Android app:

By autocompleting addresses as a user enters them, we’re already pretty far along the path to achieving the goal of our first story: not entering data that you should be able to infer. But let’s take a deep dive into a few of the unique features that take the PlacesAutocompleteTextView experience to the next level.

Place history

Repeating yourself is the worst. Sure there are probably more frustrating things than repeating yourself, but in the context of checkout forms having to type the same address more than once is ludicrous, even if there’s full autocompletion for it. Fortunately, PlacesAutocompleteTextView maintains a full history of selected results from the Places API so that entering past results is a breeze.

Normally, the threshold for queuing the Places API to get results is 3 characters entered, but if the user has already previously selected a Place from the popup, the PlacesAutocompleteTextView will suggest past results. By default, it will save up to 5 places and offer them as suggestions as soon as the user starts typing. This history is stored in a file on the filesystem, so previously entered addresses will be remembered across sessions as well.

Remembering the locations that the user has previously selected, the PlacesAutocompleteTextView is checking the box next to our second user story, “I do not want to type the same information more than once”.

Location biasing

154 million. That’s the number of postal addresses in the United States [source]. That’s a lot. When the user starts typing their address, they’re reducing the set of potential addresses significantly, but not nearly enough to provide meaningful results. How can we ensure that the most relevant addresses are surfaced to the user? There are probably a few good heuristics for this problem, but by default the PlacedAutocompleteTextView biases the place results to the location of the user. So if you’re in New York at the SeatGeek office, “235 Park” will complete with 235 Park Avenue South, New York, NY.

Location biasing can be done two ways. By default the Google Maps API will bias autocomplete results based on the IP of the device. This is a reasonably good method for biasing, but IP address lookup might not be more accurate than a few miles and lookup can break completely when used in conjunction with Proxies/VPN’s/etc. Fortunately, most Android devices provide an easy (and more accurate) mechanism for determining location: GPS.

The PlaceAutocompleteTextView exposes an API for biasing location results to an Android Location returned from the Android location APIs. This allows for highly accurate biasing of location results and ensures that the most relevant addresses are at the top of the results list. This nails the user expectations outlined in the third user story, “you should already know where I am when I need to enter my address”.

What about Google Play Services’ Places API?

Google recently launched Place APIs as part of Google Play Services. They provide a pretty clean and similar API to accessing the Places Autocompletion API as our library does, but there are two primary reasons we haven’t migrated to using it yet:

  1. It’s still impossible to fetch the full Place Details from the Google Play Services implementation, without which you cannot get the full breakdown of address components, just a human readable address string
  2. Our implementation handles binding the data to UI components to accelerate the development process by not having to implement all the UI yourself. With Google Play Services, you would still need to create your own FilterAdapter, AutocompleteTextView, etc.

Wrap up

By combining Google’s Places Autocomplete API with the power of Android’s local storage and location features, we’re able to provide a great and painless address entry experience in our checkout flow. We’re also able to meet all of our goals of minimizing the character entry count, remembering previously entered addresses and suggesting the best addresses with the highest degree of relevancy to our users.

PlacesAutocompleteTextView is open source today and available on maven, so add it to your checkout flow and improve the form experience for your users tomorrow.

– SeatGeek Android Team

P.S. If a UX-driven product development process like ours is something that appeals to you, we’re hiring.

On the Train to Success: SeatGeek’s Experience With NYC Subway Ads

New York is packed with live events in the fall; the U.S. Open takes place in Flushing and the Jets and Giants kick off their seasons. With over six million daily riders, the NYC subway offered us an interesting advertising opportunity to reach fans in the largest entertainment market at the best time of the year for live events.

We’ve experimented with out of home advertising before, but nothing at the scale of the New York City subway. It was a big investment for us, but one that we’re ultimately glad we made, and we want to share a few things we learned along the way…

Creative and Design – Putting Fans Front and Center

We care an awful lot about design here at SeatGeek. Our ads had to stand out while at the same time conforming to rules and laws around rights and marks. In most cases we can’t use team names or logos. So, in response, we turned our attention to the crowd for this campaign, and it didn’t take long to find inspiration in the form of the fans themselves. Fans (and their enthusiasm) are what make an event extra special, so we focused our work on showcasing the craziest fans – those extra passionate people that stand out of the crowd.

Campaign Performance

Subway campaign performance was stronger than we expected. Like most marketers, we use a handful of attribution techniques for offline marketing, but one easy-to-explain method involves simply surveying users after they have completed their first SeatGeek transaction. Predictably, users did not credit the subway before the ads went up, but upon launching our campaign, 20% of new buyers attributed the subway as the way they first heard about SeatGeek. Even since the our ads came down, we haven’t seen much of a drop – subway ads are still among our largest attributed source of new customers in New York City.

After seeing how things went with this campaign, we’ll be likely to re-invest in the subway again. However the effects of the subway campaign have stretched beyond pure dollars and cents for us. We’ve received dozens of job applications from candidates who said they applied after seeing our ads. Our brand-tracking services show a nice uptick in New York City. And on a more personal level, one of our employees even scored a date with someone who recognized our ads!

If working on a campaign like this is the sort of thing that would get you fired up, consider applying for one of our open jobs! We’ve got a number of openings in design, marketing and more all listed on our jobs page.

Managing Application Server Dependencies With Aptfile

a simple method of defining apt-get dependencies for an application

A common pattern in application development is to create a file that contains dependencies for running your service. You might be familiar with some of these files:

  • package.json (node)
  • requirements.txt (python)
  • Gemfile (ruby)
  • composer.json (php)
  • Godeps.json (golang)

Having a file that contains dependencies for an application is great1 as it allows anyone to run a codebase and be assured that they are running with the proper library versions at any point in time. Simply clone a repository, run your dependency installer, and you are off to the races.

At SeatGeek, we manage a number of different services in various languages, and one common pain point is figuring out exactly how to build these application dependencies. Perhaps your application requires libxml in order to install Nokogiri in Ruby, or libevent for gevent in Python. It’s a frustrating experience to try and setup an application, only to be given a Cthulu-like2 error message about how gcc failed at life:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Installing collected packages: gevent, greenlet
Running setup.py install for gevent
building 'gevent.core' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -I/opt/local/include -fPIC -I/usr/include/python2.7 -c gevent/core.c -o build/temp.linux-i686-2.7/gevent/core.o
In file included from gevent/core.c:253:0:
gevent/libevent.h:9:19: fatal error: event.h: No such file or directory
compilation terminated.
error: command 'gcc' failed with exit status 1
Complete output from command /var/lib/virtualenv/bin/python -c "import setuptools;__file__='/var/lib/virtualenv/build/gevent/setup.py';exec(compile(open(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --single-version-externally-managed --record /tmp/pip-4MSIGy-record/install-record.txt --install-headers /var/lib/virtualenv/include/site/python2.7:
running install

running build

running build_py

running build_ext

building 'gevent.core' extension

gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -I/opt/local/include -fPIC -I/usr/include/python2.7 -c gevent/core.c -o build/temp.linux-i686-2.7/gevent/core.o

In file included from gevent/core.c:253:0:

gevent/libevent.h:9:19: fatal error: event.h: No such file or directory

compilation terminated.

error: command 'gcc' failed with exit status 1

----------------------------------------
Command /var/lib/virtualenv/bin/python -c "import setuptools;__file__='/var/lib/virtualenv/build/gevent/setup.py';   exec(compile(open(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --single-version-externally-managed --record /tmp/pip-4MSIGy-record/install-record.txt --install-headers /var/lib/virtualenv/include/site/python2.7 failed with error code 1 in /var/lib/virtualenv/build/gevent
Storing complete log in /home/vagrant/.pip/pip.log.

While projects like vagrant and docker can help alleviate this to an extent, it’s sometimes useful to describe “server” dependencies in a standalone file. Homebrew users can use the excellent homebrew-bundle - formerly brewdler - project to manage homebrew packages in a Brewfile like so:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env ruby

tap 'caskroom/cask'
cask 'java' unless system '/usr/libexec/java_home --failfast'
brew 'elasticsearch'
brew 'curl'
brew 'ghostscript'
brew 'libevent'
brew 'mysql'
brew 'redis'
brew 'forego'
brew 'varnish'

Simply have a Brewfile with the above contents, run brew bundle in that directory on your command-line, and you’ll have all of your application’s external dependencies!


Unfortunately, this doesn’t quite solve the issue for non-homebrew users. Particularly, you’ll have issues with the above approach if you are attempting to run your application against multiple non-OS X environments. In our case, we may run applications inside both a Docker container and a Vagrant virtual machine, run automated testing on Travis CI, and then deploy the application to Amazon EC2. Re-specifying the server requirements multiple times - and keeping them in sync - can be a frustrating process for everyone involved.

At SeatGeek, we’ve recently hit upon maintaining an aptfile for each project. An aptfile is a simple bash script that contains a list of packages, ppas, and initial server configurations desired for a given application. We can then use this to bootstrap an application in almost every server environment, and easily diff it so the operations team can figure out whether a particular package is necessary for the running of a service3.

You can install the aptfile project like so:

1
2
3
# curl all the things!
curl -o /usr/local/bin/aptfile https://raw.githubusercontent.com/seatgeek/bash-aptfile/master/bin/aptfile
chmod +x /usr/local/bin/aptfile

We also provide a debian package method in the project readme for those who hate curling binaries.

To ease usage across our development teams, a dsl with a syntax similar to bundler was created. The aptfile project has a few primitives built-in that hide the particulars of apt-related tooling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env aptfile
# ^ note the above shebang

# trigger an apt-get update
update

# install a packages
package "build-essential"

# install a ppa
ppa "fkrull/deadsnakes-python2.7"

# install a few more packages from that ppa
package "python2.7"
package "python-pip"
package "python-dev"

# setup some debian configuration
debconf_selection "mysql mysql-server/root_password password root"
debconf_selection "mysql mysql-server/root_password_again password root"

# install another package
package "mysql-server"

# we also have logging helpers
log_info "🚀  ALL GOOD TO GO"

One potential gripe behind a tool like this is that it would lock you into the dsl without being very expressive. Fortunately, an aptfile can contain arbitrary bash as well:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env aptfile

update

# pull down a debian package from somewhere
curl -o /tmp/non-repo-package.deb https://s3.amazonaws.com/herp/derp/non-repo-package.deb

# install it manually
dpkg -i /tmp/non-repo-package.deb

# continue on

Another issue we found is that tooling like this can either be overly verbose 4 or not verbose enough 5. The aptfile project will respect a TRACE environment variable to turn on bash tracing. Also, if there is an error in any of the built-in commands, the log of the entire aptfile run will be output for your convenience.

For the complete documentation, head over to the Github repo. We hope you’ll be able to use bash-aptfile in creating a better, faster, and smoother developer experience.


  1. Though having multiple files for each language can be frustrating ;)

  2. https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454

  3. We actually manage service OS dependencies in a separate manifest for each service. In our case, this isn’t currently stored with the service being managed, leading to cases where a service is deployed but an underlying OS dependency doesn’t exist. The other issue we have is while we do have good records as to what extra dependencies a service needs - such as libmysqlclient-dev - it’s not always clear what initial packages are needed - such as a specific version of php. It’s very easy to install a library globally across your infrastructure and then forget where it’s used when migrating CI services :)

  4. Ever try figuring out what npm install is actually doing on verbose logging? Sometimes tooling outputs to stdout when it should go to stderr or vice-versa, resulting in a painful debugging experience.

  5. Suppressing output via -q flags should never suppress errors, and while this is almost never the case, sometimes you need to redirect all output to /dev/null in order to get rid of stuff like WARN deprecated gulp-clean@0.2.4: use gulp-rimraf instead. Not that you should ever pipe anything of importance to /dev/null.

Introducing Sixpack-java: A/B Testing for Android and Java Apps

A Java client for the Sixpack A/B testing framework

The SeatGeek engineering team is excited to announce the latest addition to the Sixpack A/B testing framework’s client list: sixpack-java. Designed with the goal of making A/B testing Android applications easy and painless, sixpack-java has a straightforward API and an easy setup process that should make measuring and analyzing your application design decisions a breeze.

If you’re unfamiliar with Sixpack or A/B testing in general, you can read more about it here.

Let’s take a look at how you might integrate sixpack-java into your Android app.

Android app integration

Note: it is assumed that before integrating the java client into your app that you have set up a running instance of Sixpack-server; for information on setting one up, check out the instructions here

First, you’ll need to add sixpack-java to your application dependencies. sixpack-java is available on the Sonatype snapshots repository while it’s in beta, so you’ll need to add the following to your build.gradle:

1
2
3
4
5
6
7
repositories {
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}

dependencies {
    compile 'com.seatgeek:sixpack:0.1-SNAPSHOT'
}

Now that you’ve resolved your dependencies, you can add an A/B test to your app. Let’s initialize a Sixpack client by creating a new Sixpack instance using the SixpackBuilder:

1
2
3
4
Sixpack sixpack = new SixpackBuilder()
        .setSixpackUrl("http://api.mycompany.com/sixpack")
        .setClientId(getCachedClientId())
        .build();

For reference, your getCachedClientId() method might look something like this:

1
2
3
4
5
6
7
8
9
public String getCachedClientId() {
    SharedPreferences prefs = context.getSharedPreferences("sixpack", Context.MODE_PRIVATE);
    String clientId = prefs.getString("sixpack_client_id");
    if (clientId == null) {
        clientId = Sixpack.generateRandomClientId();
        prefs.edit().put("sixpack_client_id", clientId).apply();
    }
    return clientId;
}

There are a two important things to take note of here: - You need to replace the url used in the setSixpackUrl() call with the url that points to your Sixpack-server deployment - The client id. The client id is the identifier used by the Sixpack-server to keep track of which clients have received which alternatives in the experiments they’re participating in. It is very important that the client id doesn’t change between sessions or else your users may see one alternative one time visiting the app and then another the next. That will probably have a fairly significant impact on your results too, so, just don’t do it. We recommend generating the client id once (there’s a helper method available, Sixpack.generateRandomClientId()) and caching that value in SharedPreferences so that it can be used again later (see the above code for an example of how that might work).

Note: we recommend maintaining a singleton instance of Sixpack using your favorite DI implementation, we use dagger for this

Alright, now you can create an experiment for testing the color of a button in your UI:

1
2
3
4
5
Experiment buttonColor = sixpack.experiment()
        .withName("Button Color")
        .withAlternative(new Alternative("Red"))
        .withAlternative(new Alternative("Green"))
        .build();

This will create a new experiment called “Button Color” with two alternatives, “Red” and “Green”. Once your client starts participating in this experiment, you’ll see it show up in the Sixpack-web dashboard. So let’s start it!

You start the test by calling Experiment#participate() and passing in the appropriate callbacks. Java 8 lambdas are used here for brevity.

1
2
3
4
5
6
7
8
9
10
buttonColor.participate(
        (participatingExperiment) -> {
            // success! save the participating instance for later so that we can convert it and set our button color
            this.participatingExperiment = participatingExperiment;
            button.setBackgroundColor(participatingExperiment.selectedAlternative == redAlternative ? R.color.button_red : R.color.button_green);
        },
        (experiment, error) -> {
            // failure, check network connection and try to participate again, you should also likely fallback to a default
        }
);

Now your test is live in the dashboard!

Finally, when the user clicks the button (assuming that’s the action that you’re measuring in this example) you can go ahead and fire the convert() message to Sixpack.

1
2
3
4
5
6
7
8
9
10
public void onClick(View button) {
   participatingExperiment.convert(
            (convertedExperiment) -> {
                // success!
            },
            (experiment, error) -> {
                // failure, check network connection and try to convert again
            }
    );
}

And that’s it, you’ve now successfully tested that button color!

Advanced usage

You might take a look at this API and say that there are several calls that do not need to be repeated more than once, and you’re not wrong! For instance, if you have a heavily trafficked part of your app under test, it will be to your advantage to call participate early in your application’s lifecycle and cache the ParticipatingExperiment in a way that will make fetching the selected alternative and calling convert() as easy as possible as to not hold up the rest of your UI from initializing. That said, participating early is risky and isn’t appropriate for most cases because you don’t want to participate in a test that your user never gets a chance to convert. Be careful and be sure to validate your tests are calling participate and convert at the right times before going into production by using a proxy or logging.

Here are a few “advanced usage” scenarios that you may want to consider for improving the sixpack-java integration in your app:

  1. You could expose your ParticipatingExperiments as RxJava Observables and .cache() the results
  2. In addition to having a singleton Sixpack instance in DI, you can put your Experiments and ParticipatingExperiments in your dagger modules so that they can have a lifecycle outside some of your application’s components
  3. Instead of generating a random client id, for your users that are registered and have ids within your own system, you can use their user uuid as the Sixpack client id and later use Sixpack-server’s API to join your A/B testing results with your application’s users!

Conclusion

We can’t wait to see the great things you’ll build with sixpack-java! We’ve been using sixpack-java internally, but it is beta software at the moment, so please integrate it into your apps and let us know if you have any issues with it here at our github.

Have fun building!

P.S. If you’re interested in helping us create the best mobile event ticketing experience on Android, we’re hiring.

SeatGeek 3.0 for Android

Today marks the initial release of SeatGeek 3.0 for Android. Much has changed since 2.0 was released last fall, all in the spirit of making it easier for you to do everything you love to do with SeatGeek. You can track your favorite teams and artists, see high-res views from every section of a venue, and — of course — buy tickets.

Let’s take a look at what’s changed since our last major release.

Save your payment and shipping information, and buy tickets in seconds. New in today’s release is a feature that eliminates everyone’s least favorite part of buying something on your phone — having to enter your billing and shipping information … Every. Single. Time. With SeatGeek 3.0 for Android, you can store your credit cards and delivery addresses. When you find the perfect tickets, just select a saved card and address, click “Buy Now”, and presto, you’re done! Ship tickets to your home, office, or a friend, and never worry about entering the same information twice.

Import your favorite teams and artists. We’ve added the ability for you to connect your Facebook account and favorite music services to your SeatGeek account. Once you’ve connected, SeatGeek will provide you personal event recommendations based on the teams and music you love, and you’ll receive notifications about games and shows near you.

Find and edit important account information from the redesigned Settings view. As part of a broader material design integration, we’ve updated Settings in today’s release to make navigating the details that power your SeatGeek experience simpler and more intuitive. You can come here to edit your location or add favorite performers from connected services for new event recommendations. All your payment information is accessible here as well in case you need to add, delete or update a credit card or delivery address.

Get money back with promo codes. Maybe you’ve heard a SeatGeek ad on the radio, received a card in the mail, or seen us on the subway. Notice the promo code in those? Now you can enter it in the app and have that offer instantly added to your account. Next time you grab tickets for an event, your rebate is automatically triggered. Same tickets, even better deals. Nice!

See the view from your seat — before you get there. This fresh design lets you feel what it will be like to be at your event with full-width view-from-seat images. Without even having to scroll, you’ll also see key information about the event, seat location, seller, and ticket price. You can change the number of tickets you’d like and choose from your saved payment and delivery addresses, or stick to the defaults and buy immediately from this screen.

The updated Android app is rolling out to users this week in the Play Store, so check it out when you get the update, and let us know what you think!

Stretching a Dollar at the Home Run Derby

Most people go to the Home Run Derby to see one thing: star players hitting long bombs. They probably hope to come away with a souvenir, too, which is why some of the priciest tickets for the derby every year are those in the outfield.

Knowing that fans buy tickets in home run territory at a premium, we set out to build a tool that shows which sections at this year’s derby give fans the best chance of catching a baseball — and based on the going rate for tickets in those sections, which represent the best value for home run hunters.

If you’d prefer to cut to the chase and see the results, you can check out our finished Guide to the 2015 Home Run Derby tool here.

Method

Once the participants in this year’s derby were announced, the first step in our process was to use ESPN’s Home Run Tracker (major h/t!) to map their home runs to Great American Ball Park.

Next, we went about predicting the number of home runs we expected to be hit in this year’s competition. We accounted for several factors, including the new bracket-style format, the average amount of home runs hit at the derby historically, and where this year’s hitters’ have hit home runs in 2015. In the end, we came to an expected homer count of 89, 67 of which we expected to be catchable (meaning that they would land in a seating section, rather than the non-spectator areas in center field).

We then distributed those predicted home runs by section in proportion to the distribution of actual home runs that have been hit by the eight participants in MLB games this year. This gave us an expected number of home runs per section, and we divided the average resale price for each section by its expected home runs to come up with a relative value (“HR cost” in the table).

Interesting Findings

This year’s class of hitters distribute their home runs relatively fairly, with right field and left field seeing a similar number of home runs. Manny Machado and Josh Donaldson have hit homers to both corners of the park this year, while Kris Bryant hits most of his home runs to center and left-center field.

Overall, the section with the best value is Section 105, located down the left field line. We expect around 12 home runs to land in this section during the derby, which partially explains why it has the fourth-highest ticket prices among sections in the outfield.

With no access to dead center field, it seems as though many home run balls will end up uncatchable.

While the corners are certainly the top spots to expect home runs, interestingly it is the right-center Section 144 and left-center Section 102 where we expect the second- and third-most home runs to be hit. Fans in Section 102 should pay particular attention when Todd Frazier is at the plate, while those in Section 144 should watch out when Joc Pederson is swinging.


The only sections where we highly doubt a home run ball will reach are Sections 402 and 403, located in the second level of center field. None of this year’s participants have hit a home run this year that would land in this area of the park. The fans in those sections should pay particular attention when Albert Pujols is at the plate, though, as this year he has shown the best ability to get the ball to fly that far to left.

Potential Pitfalls

Predicting the amount of home runs hit at the derby, and where they will land, is certainly an inexact science.

Below are just some of the potential problems:

  • We didn’t take into account each batter’s odds to advance, which would have impacted the distribution of predicted home runs.

  • We didn’t account for weather — the forecast for the derby is not ideal, and that could have a serious impact on how the ball travels.

  • We mapped home runs based on major league pitching, not the batting-practice soft toss batters will actually face Monday night. If we had a reliable data set on where these hitters have launched BP homers, that might have been a useful input.

  • With a new format this year, the historical data on how many home runs have been hit at previous derby competitions was less directly applicable. We had to guesstimate the amount of swings each batter will take in the five-plus-minute at-bats, and then go from there to figure out a total number of expected home runs.

Big shoutout to former SeatGeek intern Josh Rosenfeld, who built the original version of this project last summer.

Animation: Giving Life to Live Events

Visual aesthetic is inherently a part of any live event experience, whether it’s a sporting event, concert or Broadway show. We’ve always focused on trying to capture the rich imagery of sports and music with performer photos, detailed venue mapping and view-from-seat photos, but we’ve recently begun to bring our live event visuals to life as well.

If you’re a SeatGeek user and fan of an NBA or NHL playoff team, chances are you’ve seen one or more of our animations in your inbox. What I’d like to do here is share some insight into my design process and hopefully inspire you to explore the tools and resources out there that you can use to create animations of your own.


Getting started

Before putting pen to tablet, I like to look around and see what’s out there for both inspiration and reference material. When thinking about imagery for our NBA and NHL playoff emails, I turned to sports blogs and news sources for photo references, but my go-to sites are dribbble, niice.co and designsperation.

Sketching

This is the most important step in my design process. It helps eliminate any lame ideas and figure out what will and will not work. Personally, I love using a Wacom tablet and Photoshop; you can duplicate ideas and mess things up (in a good way) very quickly.

Typically, an idea starts out as either mega-rough, rough, or close-but-no-cigar, but if the feeling is right with even a mega-rough idea, you can often skip the latter two stages and move straight into Illustrator.

Artwork

At this point, there is still some decision making going on: Can I remove anything? Can I add anything? “Less but better” is a mantra I try to live by. It’s easy to confuse simplicity with lacking detail and difficult to strike a balance between the two. It’s easy to get caught up fussing over minutiae; I often find myself in the land of endless-possibilities-and-details.

I still watch a lot of tutorials on creating vector art, because there’s always something to new to learn. I highly recommend http://www.pixelcasts.io/ if you’re looking to soak up some valuable Adobe Illustrator knowledge.

Animation

Going into the animation process, what I try to do is reduce the number of layers and groups in Illustrator. Keeping the shoulders, arms, stick and hands all together makes life much easier once you get this into After Effects.

We can create simple layer structures in Illustrator that will then be matched in After Effects after importing.

If you’re interested in learning more, I can’t recommend this Skillshare class on Simple Character Animation enough. It’s where I learned most of what I know about After Effects animation!

Digital Groundskeeping: Redefining ‘Attention to Detail’ in Our Baseball Seating Charts

If you’ve read even a handful of product-related posts on ChairNerd, this isn’t news to you: at SeatGeek, user experience is king. It’s why we’re intensely focused on continually improving the quality of our seating charts, which should be the ultimate source of context in the process of purchasing a ticket.

Recently, we’ve enhanced the accuracy of the playing surfaces within our NFL, NBA, and NHL maps. But with spring comes the perfect opportunity to spruce up our MLB ballparks, which are perhaps the most individualized set of major sports venues in the U.S.

The goals for these enhancements went beyond the aesthetic. We wanted to make our maps as true to reality as possible, which is why we went to the official rulebook to draw the regulation lines and markings of an MLB diamond:


Before & After: Yankee Stadium (NY Yankees)

Before After Satellite image


Before & After: Progressive Field (Cleveland Indians)

Before After Satellite image

But there’s more to groundskeeping than abiding by the rules of 90 feet and 60 feet, 6 inches. Each ballpark’s infield, outfield and on deck areas are unique, and we’ve incorporated those custom designs into the maps of all 30 MLB venues.

Custom infields

The cut of the grass is just one variable when it comes to an infield – even the shape of the dirt varies from ballpark to ballpark. Fenway has its historic fungo circles; Comerica Park is one of a few parks with a dirt path between home plate and the pitcher’s mound; Rogers Centre has the greenest infield in the big leagues, but no grass. Here are a few of my personal favorite infield renderings:

Boston Red Sox
(Fenway Park)
Cleveland Indians
(Progressive Field)
Detroit Tigers
(Comerica Park)
Houston Astros
(Minute Maid Park)
New York Yankees
(Yankee Stadium)
Toronto Blue Jays
(Rogers Centre)

Custom grass designs

Over time, the grass at MLB ballparks has increasingly become an artistic outlet for head groundskeepers:

David R. Mellor, director of grounds for the Boston Red Sox, has in fact written an authoritative textbook on the subject:

Groundskeepers typically alternate designs throughout the season, but the following are frequently used in each of the 30 MLB parks:

Arizona Diamondbacks
(Chase Field)
Atlanta Braves
(Turner Field)
Baltimore Orioles
(Camden Yards)
Boston Red Sox
(Fenway Park)
Chicago Cubs
(Wrigley Field)
Chicago White Sox
(US Cellular Field)
Cincinnati Reds
(Great American Ball Park)
Cleveland Indians
(Progressive Field)
Colorado Rockies
(Coors Field)
Detroit Tigers
(Comerica Park)
Houston Astros
(Minute Maid Park)
Kansas City Royals
(Kauffman Stadium)
Los Angeles Angels
(Angel Stadium)
Los Angeles Dodgers
(Dodger Stadium)
Miami Marlins
(Marlins Park)
Milwaukee Brewers
(Miller Park)
Minnesota Twins
(Target Field)
New York Mets
(Citi Field)
New York Yankees
(Yankee Stadium)
Oakland A’s
(Oakland Coliseum)
Philadelphia Phillies
(Citizens Bank Park)
Pittsburgh Pirates
(PNC Park)
San Diego Padres
(Petco Park)
San Francisco Giants
(AT&T Park)
Seattle Mariners
(Safeco Field)
St. Louis Cardinals
(Busch Stadium)
Tampa Bay Rays
(Tropicana Field)
Texas Rangers
(Globe Life Park)
Toronto Blue Jays
(Rogers Centre)
Washington Nationals
(Nationals Park)


This project is just one small part of our mission to create the best seating charts in the world by redefining the interpretation and visualization of architectural and geographical data. If you have a background in architecture, design, or art and want to help us, perhaps you’re our next Digital Architect!

Bid Automation on the Adwords API

A data-backed adwords campaign bidder

Adwords is great, but its built-in tools don’t always allow you to solve problems unique to your business. I was recently inspired by this post on Search Engine Land to build something similar off the Google Ads API.

There were a few problems on our account that couldn’t be solved by the Brainlabs solution.

  1. Because of the way our checkout process is structured, Adwords doesn’t know about our transactions. Until recently, this hadn’t been a problem; we have an approximation for transactions set up in Google Analytics, and the main KPIs for most of our campaigns are app downloads and email collection (things Adwords is aware of). But I wanted to use real-time transactional information to inform our bidding.
  2. I didn’t want to set up different scripts or bid modifier charts for each of our campaigns. People behave differently when buying Broadway tickets than they do when buying NBA tickets, and I wanted something that would calculate modifiers on the fly for each campaign.
  3. Finally, the major sports teams are spread out across three time zones. The “right time” to bid up a campaign for the Los Angeles Dodgers is different than for the New York Yankees, so I needed something that could handle those differences as well.

A few months back, we had our quarterly Hackathon and I decided to explore the Adwords API, which is pretty easy to sign up for.

There are several Google Ads API client libraries, like PHP, Ruby, Java, etc. I settled on the Python Libary because I hadn’t ever built anything in Python. This will become abundantly clear when you dive into my code.

You can find the script and instructions on how to set it up here. There are three major components of the script.

  1. Pulling transactions or whatever KPI you’re interested in from your database
    • We use Amazon Redshift, so my script uses the Psycopg2 package to connect to and query our database. If you’re running off MySQL, you can follow the instructions here in order to connect to your DB.
    • Make sure that any reporting you do is in the same time zone as your Adwords account. For example, our database records timestamps in UTC, but our Adwords account runs everything on EST.
  2. Working with the Adwords API
    • This file, adwords_api.py, contains every Adwords API-related call, from getting spend reports to modifying keyword bids.
    • This whole operation only runs on campaigns with a specific label. There are instructions here on how to find your label IDs, or how to modify the script so it runs on every campaign on your account.
  3. Modifying bids
    • This file, create_bids.py, actually calculates the bid modifier for each hour based on an ROI or CPA goal defined by you.

What will future versions do?

  • I’d like to write versions that work on the adgroup and keyword level. Currently, the script only calculates modifiers on the campaign level and then attaches them to every keyword in said campaign.
  • Right now, this script doesn’t take day of week into account. While conversion rate from SEM doesn’t change much day to day, I understand that day of week could be meaningful for some businesses. For SeatGeek’s purposes in particular, a future version that integrates time-to-next-event for each team would be a big improvement.

While this is certainly a work in progress, I hope this script can act as a jumping-off point for others to build something that works for their needs.