As of October 1, 2020, Scholar Snapp Central API functionality is hosted by the College Board. See the College Board Website for details.

API Example

The following describes, as a set of examples, the full process of requesting access from the user, acquiring an authentication code and access token, and downloading and opening the user’s Scholar Snapp Profile XML. The code is presented as Microsoft .NET 4.5 C# code, but any code that performs the required requests may be used.

Client Application Settings

To understand the example, it is important to define some basic variables.

  • The scholarship provider application (or “client application”) is located at https://www.example.org/.
  • The application has a page at https://www.example.org/scholarsnappconnect which is used as the redirect URI.
  • The application’s client ID is “snapp_auth_test” and its client secret is “09c74ca87c55468daefe68be15d2ba69”.

First HTTP Request: Requesting Access from the Applicant

The scholarship provider application presents a button to the user, with the following HTML markup. Note that any method that directs the user’s browser to the required URL will work.

<a ref="https://auth.scholarsnapp.org/oauth/Authorize?response_type=code&client_id=example.org&redirect_uri=http%3A%2F%2Fwww.example.org%2Fscholarsnappconnect&state=5CD4CED7-965C-452B-A5F5-AC957DAD7542">Connect to Scholar Snapp</a>

The link could be on an image (e.g., the Scholar Snapp logo) or could be styled in order to fit the client application’s style guide.

Second HTTP Request: Processing Scholar Snapp Import

If the user authorizes the client application to access their profile, the user’s browser will be redirected to:

https://www.example.org/scholarsnappconnect?code=SplxlOBeZQQYbYS6WxSbIA&state=5CD4CED7-965C-452B-A5F5-AC957DAD7542.

The state parameter is optional, and its value is returned to client systems exactly as passed to the Scholar Snapp API. The value can be used by client systems, for example, to restore the state of an application and/or to prevent cross-site request forgery (CSRF) attacks.

That page should make a POST request to the Scholar Snapp /oauth/token endpoint and parse the JSON response, for example using code similar to the following:

string client_id = "snapp_auth_test";
string client_secret = "09c74ca87c55468daefe68be15d2ba69"; 
string scholarSnappBaseUri = "https://auth.scholarsnapp.org";
string clientAppRedirectUri = "https://www.example.org/scholarsnappconnect";

var grantType = "authorization_code";
var url = scholarSnappBaseUri + "/oauth/Token";

// Create the form body for the POST 
var data = new StringBuilder();
data.Append("grant_type=" + Uri.EscapeDataString(grantType)); 
data.Append("&code=" + Uri.EscapeDataString(code)); 
data.Append("&redirect_uri=" + Uri.EscapeDataString(clientAppRedirectUri));

using (var webClient = new WebClient())
{
	webClient.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(client_id + ":" + client_secret)));
	webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");

	var json = webClient.UploadString(url, data.ToString());

	JavaScriptSerializer ser = new JavaScriptSerializer(); 
	Dictionary<string, object> x = (Dictionary<string, object>)ser.DeserializeObject(json);
	var accessToken = x["access_token"].ToString();
	var expiresIn = int.Parse(x["expires_in"].ToString()); // In seconds
}

A real application will need to handle any errors or exceptions that occur during any of the requests presented in this example.

Once the application has the access token, a simple GET request to the API endpoint will retrieve the Scholar Snapp Profile for the user:

string scholarSnappBaseUri = "https://auth.scholarsnapp.org"; 
var url = scholarSnappBaseUri + "/api/profile/snappfile?version=4"; 
byte[] scholarSnappFile;

using (var webClient = new WebClient())
{
	webClient.Headers.Add("Authorization", "Bearer " + authToken);

	scholarSnappFile = webClient.DownloadData(url);
	// The scholarSnappFile variable contains the raw byte stream of the compressed SDS file.
}

And the application can read it using any XML library:

XNamespace sds = XNamespace.Get("https://scholarshipproviders.org/scholarshipapplication");

var profile = new ScholarSnappProfile();

using (var memStream = new MemoryStream(scholarSnappFile))
{
	if (memStream.CanSeek)
	memStream.Seek(0, SeekOrigin.Begin);
	using (var reader = new XmlTextReader(memStream))
	{
		reader.MoveToContent();
		var xml = XDocument.Load(reader);
		var documentElement = xml.Element(sds + "ScholarshipApplication"); 
		var contextElement = documentElement.Element(sds + "Context");
		var studentElement = documentElement.Element(sds + "Student"); 
		var element = studentElement.Element(sds + "Name");
		if (element != null)
		{
			profile.FirstName = GetValueIfPresent<string>(element.Element(sds + "FirstName"));
			profile.MiddleName = GetValueIfPresent<string>(element.Element(sds + "MiddleName"));
			profile.LastName = GetValueIfPresent<string>(element.Element(sds + "LastName"));
			profile.Suffix = GetValueIfPresent<string>(element.Element(sds + "Suffix"));
			profile.PreferredName = GetValueIfPresent<string>(element.Element(sds + "PreferredName"));
		}

		element = studentElement.Element(sds + "ContactInfo"); 
		if (element != null)
		{
			profile.Email = GetValueIfPresent<string>(element.Element(sds + "Email"));
		}
		profile.DateOfBirth = GetValueIfPresent<DateTime?>(studentElement.Element(sds + "BirthDate"));
		profile.Sex = GetValueIfPresent<string>(studentElement.Element(sds + "Gender"));
		reader.Close(); 
		memStream.Close();
	}
}

GetValueIfPresent is a convenience method for reading an XML element that may or may not exist. It is reproduced below.

private T GetValueIfPresent<T>(XElement element)
{
	if (element == null || element.IsEmpty || string.IsNullOrWhiteSpace(element.Value)) return default(T);

	var underlyingType = Nullable.GetUnderlyingType(typeof(T)); 
	if (underlyingType != null)
	{
		return (T)Convert.ChangeType(element.Value, underlyingType);
	}
	return (T)Convert.ChangeType(element.Value, typeof(T));
}

In this case, only a small subset of properties are read from the file. In real code, many more properties would be read and additional error handling code would be required.

To send any updated information back to Scholar Snapp to allow the applicant to update their profile, the same API endpoint can be used with a POST request:

var authToken = (string)Session["authToken"];
var url = scholarSnappBaseUri + "/api/profile/snappfile?version=4";

string message = null;
byte[] profileData = GenerateSnappFile(); 
using (var webClient = new WebClient())
{
	webClient.Headers.Add("Authorization", "Bearer " + authToken);
	var json = Encoding.UTF8.GetString(webClient.UploadData(url, profileData));
	JavaScriptSerializer ser = new JavaScriptSerializer(); 
	Dictionary<string, object> x = (Dictionary<string, object>)ser.DeserializeObject(json); 
	message = x["message"].ToString();
}

The GenerateSnappFile method would need to take your internal data representation and produce a byte array representing the Scholar Snapp XML. An implementation covering a small portion of the Scholar Snapp Profile is shown below.

public byte[] GenerateSnappFile(ApplicantProfile profile)
{
	var xmlns = XNamespace.Get("https://scholarshipproviders.org/scholarshipapplication1.0");
	XDocument xmlDocument = new XDocument(); xmlDocument.Declaration = new XDeclaration("1.0", "UTF-8", "");

	var documentElement = new XElement(sds + "ScholarshipApplication", new XAttribute(XNamespace.Xmlns + "sds", sds));
	xmlDocument.Add(documentElement);

	var contextElement = new XElement(sds + "Context");
	contextElement.Add(new XElement(sds + "Created", DateTime.Now.ToString("O")));
	contextElement.Add(new XElement(sds + "CreatedBy", "Scholar Snapp API Sample"));
	contextElement.Add(new XElement(sds + "LastUpdated", DateTime.Now.ToString("O")));
	contextElement.Add(new XElement(sds + "LastUpdatedBy", "Scholar Snapp API Sample"));
	contextElement.Add(new XElement(sds + "ApplicationLanguage", "en-US"));
	documentElement.Add(contextElement);

	var studentElement = new XElement(sds + "Student"); 
	var nameElement = new XElement(sds + "Name");
	nameElement.Add(new XElement(sds + "FirstName", profile.FirstName)); 
	if (!string.IsNullOrEmpty(profile.MiddleName)) nameElement.Add(new XElement(sds + "MiddleName", profile.MiddleName)); 
	nameElement.Add(new XElement(sds + "LastName", profile.LastName));
	if (!string.IsNullOrEmpty(profile.PreferredName)) nameElement.Add(new XElement(sds + "PreferredName", profile.PreferredName)); 
	studentElement.Add(nameElement);

	var contactInfoElement = new XElement(sds + "ContactInfo"); 
	contactInfoElement.Add(new XElement(sds + "Email", profile.Email));
	studentElement.Add(contactInfoElement);

	if (profile.DateOfBirth.HasValue) studentElement.Add(new XElement(sds + "BirthDate", profile.DateOfBirth.Value.ToString("yyyy'-'MM'-'ddKK"))); 
	
	documentElement.Add(studentElement);
	
	using (var stream = new MemoryStream())
	using (var writer = XmlWriter.Create(stream))
	{
		xmlDocument.WriteTo(writer); 
		writer.Close(); 
		stream.Close();
		return stream.ToArray();
	}
}

Contents