InBDPA API

Xunnamius (GitHub) hsccrkby0uo4
Help

Apiary Powered Documentation

Sign in with Apiary account.

InBDPA API

Introduction

We're looking for feedback! If you have any opinions or ideas, contact us on Slack.

Based on simple REST principles, the InBDPA API returns JSON data responses to requests. This is the API used by teams and their apps for the BDPA National High School Computer Competition. It contains all of the data teams' apps must interact with. The API is live and will ideally remain online indefinitely.

The base address of the InBDPA API is https://inbdpa.api.hscc.bdpa.org/V where V is the version of the API you want to use. There is currently only one version, so V = v1. Each version of the API provides a set of endpoints with their own unique path and requirements.

The source code behind the API is available on GitHub. If you have any trouble, open an issue there or contact us on Slack.

Notice: due to financial constraints, the oldest documents in the system will be dropped from the API to make room for the new. That is: <item>_ids are not guaranteed to exist forever!

Requesting a Key

To access the majority of this API's endpoints requires a key. If your team needs a key, or to replace a lost or stolen key, either use our Slack bot (BDPABot) to manage your team's keys or contact us on Slack.

When you get your key, include it as your request's Authorization header and you will be immediately authenticated into the system. For example: Authorization: bearer your-special-api-key-here.

Rules of API Access

  1. Do not bombard the API with requests or you risk permanent IP/subnet ban. Limit your apps to no more than 10 requests per second per API key. If your app ends up sending too many requests over some time period, you'll get a HTTP 429 response along with a monotonically increasing soft ban (starting at 15 minutes). Similarly, the size of requests is strictly limited, so you must limit the amount of data you're sending. When you send a request that is too large (>100KB), it will fail with a HTTP 413 response.

  2. Do not reveal your API key to anyone not on your own team. It is how the API identifies your team. Do not upload it to GitHub or leave it lying around in your source code. Save it to a file and .gitignore it or save it to an environment variable.

  3. Since the API is live, you might be able to see or interact with content posted by other teams. If this is the case, please do not post anything inappropriate.

  4. If you have a relevant feature request or you encounter any vulnerabilities, errors, or other issues, don't hesitate to contact NHSCC staff via Slack or open an issue on GitHub. For significant enough finds, bonus points may be awarded. On the other hand, abusing any vulnerability or bug may result in disqualification.

  5. The API was built to randomly return errors every so often. That means your app must be prepared to deal with HTTP 555 and other bad responses. However, if you're consistently getting HTTP 5xx errors back to back, then something is wrong. Please report this if it happens.

  6. All responses are raw JSON. All request payloads must be sent as raw JSON. JSON.stringify() and JSON.parse() or whatever language equivalent is available to you is your friend!

Request Methods

This API is based on simple REST principles. Resources are accessed via standard HTTPS requests in UTF-8 format to an API endpoint. This API understands the following HTTP request methods:

METHOD MEANING
GET Return data about something
POST Create something new
PATCH Modify something
PATCH Partially modify something
DELETE Delete something

Rate Limits

As said earlier, do not bombard the API with requests. If you do, the API will soft ban you for fifteen minutes the first time before accepting requests from your API key or IP address again. Each following time this happens within a certain period, your ban time will quadruple.

So limit your apps to no more than 10 requests per second per API key. You know you've been soft banned if you receive an HTTP 429 response. Check the JSON response for the retryAfter key, which represents for how long your API key and/or IP are banned from making further requests (in milliseconds).

If this is the first time you've been banned, you can use the Slack bot to unban yourself immediately. If the Slack bot is not available or this is not the first time you've been banned, contact us on Slack.

Pagination

Endpoints that might return a lot of items (users, documents, etc) are paginated via range queries. Such endpoints optionally accept an after parameter, which is an <item>_id or other identifier that determines which API item is returned first. That is: the first item will be the first <item>_id that comes after the after <item>_id. Omitting the after parameter returns the first 100 items in the system.

For example, given the following dataset and an API with a default result size (or "page" size) of 3:

[
    { item_id: 0xabc123, name: 'Item 1 name' },
    { item_id: 0xabc124, name: 'Item 2 name' },
    { item_id: 0xabc125, name: 'Item 3 name' },
    { item_id: 0xabc126, name: 'Item 4 name' },
    { item_id: 0xabc127, name: 'Item 5 name' },
]

Suppose we issued the following requests to an API:

/api?after=0xabc123: responds with an array of 3 items: 0xabc124 through 0xabc126 /api?after=0xabcXYZ: responds with an array of 0 items since item_id 0xabcXYZ doesn't exist /api?after=0xabc124: responds with an array of 3 items: 0xabc125 through 0xabc127 /api?after=0xabc127: responds with an array of 0 items since there is nothing after 0xabc127 /api?after=0xabc125: responds with an array of 2 items: 0xabc126 and 0xabc127

Status Codes

This API will issue responses with one of the following status codes:

STATUS MEANING
200 Your request completed successfully.
400 Your request was malformed or otherwise bad. Check the requirements.
401 Session is not authenticated. Put your API key in the header!
403 Session is not authorized. You tried to do something you can't do.
404 The resource (or endpoint) was not found. Check your syntax.
405 Bad method. The endpoint does not support your request's method.
413 Your request was too large and was dropped. Max body size is 100KB.
415 Your request was made using the wrong Content-Type header value.
429 You've been rate limited. Try your request again after a few minutes.
4xx Your request was malformed in some way.
5xx Something happened on the server that is outside your control.

Response Schema

All responses issued by the API will follow one of the two following schemas.

Success Schema

When a request you've issued succeeds, the response will look like the following:

{
    "success": "true",
    // any other data you requested
}

Note that all time data is represented as the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC, or the same thing that is returned by JavaScript's Date.now() method.

Error Schema

When a request you've issued fails, along with the non-200 status code, the response will look like the following:

{
    "error": "an error message describing what went wrong",
    // any other relevant data (like retryAfter)
}

CORS Support

The API has full support for Cross Origin Resource Sharing (CORS) for AJAX requests.

Tips for Debugging

  • Are you using the right method?

  • Use this documentation (click "see example," then click "Try console") or use Postman to play with the API.

  • Expect a raw JSON response body that you must parse manually, not raw text or something else.

  • Are you sending properly formatted JSON payloads in your request body when necessary?

  • Try outputting to stdout, use console.log, or output to some log file when API requests are made and responses received.

  • All time data is represented as the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.

  • Are you sending the correct headers? You need to specify the Authorization: bearer your-special-api-key-here header for all requests and the 'content-type': 'application/json' header when making POST and PATCH requests.

  • Are you encoding your URI components properly, especially when you're trying to send the API JSON objects via GET request?

Globally Unique IDs

To retrieve data about one or more API items, you must know that item's <item>_id. These and other IDs are globally unique within the API. That is: no two items will ever have the same ID in any instance. Use this fact to your advantage.

Reference

Info Endpoints

These endpoints allow retrieval of statistics describing the entire system.

/info (GET)

Get metadata about the entire system.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • info
    Metadata about the entire system.
    object

Opportunity Endpoints

/opportunities (GET)

Retrieves all opportunities in the system in FIFO order.

Retrievals are limited to at most 100 results per query. Supports range queries using after.

URI Parameters
after

[optional] Return only those results that occur after after in the result list.

updatedAfter

[optional] Return only those users with updatedAt greater than updatedAfter.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • opportunities
    An array of opportunity objects. Empty if there are no opportunities left to show.
    array

/opportunities (POST)

Creates a new opportunity.

Request
object
  • title
    The title of the opportunity.
    string
    "My Little Opportunity"
  • contents
    The Markdown contents describing the opportunity.
    string
  • creator_id
    The ID of the user that created the opportunity.
    string
    "5eee34b3ca37750008547375"
Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • opportunity
    The newly created opportunity object.
    object

/opportunities/:opportunity_id (GET)

Retrieve an opportunity by its opportunity_id.

URI Parameters
opportunity_id

[required] ID of the opportunity.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • opportunity
    The requested opportunity object.
    object

/opportunities/:opportunity_id (PATCH)

Update an opportunity (opportunity_id) in the system. The opportunity's updatedAt timestamp is also updated.

URI Parameters
opportunity_id

[required] ID of the opportunity.

Request
object
  • title
    The title of the opportunity.
    string, optional
    "My Little Opportunity"
  • contents
    The Markdown contents describing the opportunity.
    string, optional
  • views

    If the views should be incremented. If this property is present, it must have the value "increment".

    string, optional
    "increment"
Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

/opportunities/:opportunity_id (DELETE)

Completely and permanently remove an opportunity from the system.

URI Parameters
opportunity_id

[required] ID of the opportunity.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

Session Endpoints

/sessions (POST)

Make the API aware of an active session, which represents one client interacting with one view (view + viewed_id). Active sessions expire 30 seconds after their creation unless they are renewed, which will reset the 30-second timer.

Example usage: if user-A clicks a link to user-B's profile, which loads the Profile view, your solution would create a new session where view = 'profile' and viewed_id = user-B's-user-id. You would then continually renew that session every so often until user-A navigates away from user-B's profile.

Note that possible values for viewed_id are: a user_id (only if view === 'profile'), an opportunity_id (only if view === 'opportunity'), or null.

Request
object
  • view

    The view this session is associated with. Possible values are: "profile", "opportunity", "admin", "auth", or "home".

    string
    "profile"
  • viewed_id

    A unique immutable MongoDB ID corresponding to the view item associated with this session, or null.

    string, nullable
    "5ec8adf06e38137ff2e58770"
Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • session_id
    A unique immutable MongoDB ID representing the newly created active session.
    string

/sessions/:session_id (PATCH)

Renew an active session to indicate that a client (user) is still accessing a view. Renewing an active session resets the 30-second expiry period.

Hint: your solution should keep renewing the same session every so often until the client navigates away from the current view, closes the browser, etc. If the client navigates from one URL to another without changing views, your solution should still create a new session instead of renewing the existing one.

URI Parameters
session_id

[required] ID of the session.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

/sessions/:session_id (DELETE)

Delete (manually expire) an active session.

URI Parameters
session_id

[required] ID of the session.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

/sessions/count-for/user/:user_id (GET)

Returns the number of currently active sessions associated with the given user profile (user_id).

Each active session represents one viewer that loaded this user's profile within the last 30 seconds. If an active session is not renewed within 30 seconds, it is automatically deleted.

URI Parameters
user_id

[required] ID of the user.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • active

    The number of active sessions associated with this user's profile.

    number

/sessions/count-for/opportunity/:opportunity_id (GET)

Returns the number of currently active sessions associated with the given opportunity (opportunity_id).

Each active session represents one viewer that loaded this opportunity's profile within the last 30 seconds. If an active session is not renewed within 30 seconds, it is automatically deleted.

URI Parameters
opportunity_id

[required] ID of the opportunity.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • active
    The number of active sessions associated with this opportunity.
    number

User Endpoints

/users (GET)

Retrieves all users in the system in FIFO order.

Retrievals are limited to at most 100 results per query. Supports range queries using after.

URI Parameters
after

[optional] Return only those results that occur after after in the result list.

updatedAfter

[optional] Return only those users with updatedAt greater than updatedAfter.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • users
    An array of user objects. Empty if there are no users left to show.
    object

/users (POST)

Creates a new user.

Note that the API manages all user credentials. Passwords must NEVER be stored in any form ever (locally in your app or database or anywhere else), but are instead communicated as a special one-way "login key".

The Web Crypto API and Password-Based Key Derivation Function #2 (PBKDF2) must be used to derive this key. Here is an example using the Web Crypto API to derive a login key and salt from a password. Once this login key is derived, it and the salt must be sent to the API for storage.

NOTICE: all teams should use 100,000 iterations for PBKDF2 to make cross-app logins easier for the judges!

Request
object
  • username

    The user's unique username within the system. Must be lowercase alphanumeric (- and _ are allowed).

    string
    "thehill"
  • email

    The user's email address.

    string
    "h@hillaryclinton.com"
  • salt

    A 16-byte (32 characters) hex string representing a salt corresponding to the login key.

    string
  • key

    A 64-byte (128 characters) hex string representing a login key.

    string
  • type

    The type of this user. Possible values are: "inner", "staff", or "administrator".

    string
    "inner"
Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • user
    The newly created user object.
    object

/users/:user_id (GET)

Retrieve a user by their user_id.

URI Parameters
user_id

[required] ID of the user.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • user
    The requested user object.
    object

/users/:username (GET)

Retrieve a user by their username.

URI Parameters
username

[required] username of the user.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • user
    The requested user object.
    object

/users/:user_id (PATCH)

Update a user (user_id) in the system. Can be used to change a user's password (i.e. their key and salt), their email, and other properties. See /users (POST) for more information on the derivation of a login key and salt.

The user's updatedAt timestamp is also updated.

When updating a user's sections, the patch will be applied at the section level. For example, submitting { "sections": { "about": "My new about page" }} would overwrite the entire sections.about section, but would not overwrite sections.education, sections.volunteering, or sections.skills.

URI Parameters
user_id

[required] ID of the user.

Request
object
  • salt

    A 16-byte (32 characters) hex string representing an updated salt corresponding to the updated login key. Must be present if key is present.

    string, optional
  • key

    A 64-byte (128 characters) hex string representing an updated login key. Must be present if salt is present.

    string, optional
  • email

    The user's updated email address.

    string, optional
  • sections

    The user's updated section information.

    object, optional
  • type

    The type of this user. Possible values are: "inner", "staff", or "administrator".

    string
    "inner"
  • views

    If the views should be incremented. If this property is present, it must have the value "increment".

    string, optional
    "increment"
Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

/users/:user_id (DELETE)

Completely and permanently remove a user from the system. Note that deleting a user will not delete their opportunities.

URI Parameters
user_id

[required] ID of the user.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

/users/:user_id/auth (POST)

Attempt to authenticate the credentials of a specific user.

To check if a given username (corresponding to user_id) and password combination is valid, follow the same process as in the example to derive a login key using the salt accessible via the user endpoint. Send the newly derived login key to the API via this endpoint and, if it matches the key stored in the API, you will receive an HTTP 200 status code response. If the user credentials could not be authenticated, you will receive a HTTP 403 status code instead.

See /users (POST) for more information on the derivation of a login key and salt.

URI Parameters
user_id

[required] ID of the user.

Request
object
  • key

    A 64-byte (128 characters) hex string representing a login key derived using PBKDF#2.

    string
Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
Request
object
  • key

    A 64-byte (128 characters) hex string representing a login key derived using PBKDF#2.

    string
Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

/users/:user_id/connections (GET)

Retrieve a user's first-order connections in FIFO order.

URI Parameters
user_id

[required] ID of the user.

after

[optional] Return only those results that occur after after in the result list.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean
  • connections

    The user's first-order connections as user_ids.

    array[string]

/users/:user_id/connections/:connection_id (POST)

Add a first-order connection between users user_id and connection_id.

The user's updatedAt timestamp is also updated.

URI Parameters
user_id

[required] ID of the first user.

connection_id

[required] ID of the second user.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

/users/:user_id/connections/:connection_id (DELETE)

Remove a first-order connection between users user_id and connection_id.

The user's updatedAt timestamp is also updated.

URI Parameters
user_id

[required] ID of the first user.

connection_id

[required] ID of the second user.

Response
object
  • success

    If the request succeeded. Always true when status code is 200 and false or undefined otherwise.

    boolean

Data Structures

Info
object
  • opportunities
    The current number of opportunities in the system.
    number
  • sessions
    The current number of sessions in the system.
    number
  • users
    The current number of users in the system.
    number
  • views
    The number of views across the entire system.
    number
Opportunity
object
  • opportunity_id
    A unique immutable MongoDB ID representing this opportunity. Generated automatically by the server.
    string
    "5ec8adf06e38137ff2e58770"
  • creator_id
    A unique immutable MongoDB ID representing the user that created this opportunity.
    string
    "5ec8adf06e38e58770ff2137"
  • title
    The title of this opportunity.
    string
    "My Little Opportunity"
  • views
    The total number of views this opportunity has received.
    number
    9001
  • contents
    The Markdown contents describing this opportunity.
    string
  • createdAt

    When this opportunity was first created in milliseconds since the unix epoch. Generated automatically by the server.

    number
    1579345900650
  • updatedAt

    When this opportunity was last updated in milliseconds since the unix epoch. Generated automatically by the server.

    number
    1579345900650
UserSections
object
  • about

    The contents of a user's About section.

    string, nullable
  • experience

    The contents of a user's Experience section.

    array
  • education

    The contents of a user's Education section.

    array
  • volunteering

    The contents of a user's Volunteering section.

    array
  • skills

    The contents of a user's Skills section.

    array[string]
UserSectionEntry
object
User
object
  • user_id
    A unique immutable MongoDB ID representing this user. Generated automatically by the server.
    string
    "5ec8adf06e38137ff2e58770"
  • salt

    A 16-byte (32 characters) hex string representing a salt corresponding to the login key.

    string
    "2d6843cfd2ad23906fe33a236ba842a5"
  • username

    This user's unique username within the system. Must be lowercase alphanumeric (- and _ are allowed).

    string
    "Oforce1"
  • email

    This user's unique email address within the system.

    string
    "o@barackobama.com"
  • type

    The type of this user. Possible values are: "inner", "staff", or "administrator".

    string
  • views

    The total number of views this user's profile has received.

    number
    1234
  • sections

    This user's sectional information (e.g. Experience, Education, etc).

    object
  • createdAt

    When this user was first created in milliseconds since the unix epoch. Generated automatically by the server.

    number
    1579345900650
  • updatedAt

    When this user was last updated in milliseconds since the unix epoch. Generated automatically by the server.

    number
    1579345900650