Users
A user is a person in the MLH ecosystem — a hacker, an organizer, or anyone
who has signed in through MLH. This page covers the user resource, the handful of
special lookup endpoints (me, identify, batch_identify, phone verification),
and the sub-resources nested under a user: their addresses, participations,
organized events, and projects.
This page only covers what's specific to users. For conventions shared across the
whole API — pagination, filtering with [eq]/[contains]/etc., expand[], error
shapes, and the base URL — see Making requests. For auth
and scopes, see Authentication. Every request must be
authenticated with an OAuth 2.0 token or a bearer JWT; the user scope covers
user data.
Throughout, created_at and updated_at are Unix timestamps (seconds) in
responses. Fields like id, created_at, and updated_at are read-only —
you can't set them.
Users
The top-level person record.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v4/users | List users (paginated, filterable) |
GET | /v4/users/{id} | Get one user |
POST | /v4/users | Create a user |
PATCH / PUT | /v4/users/{id} | Update a user |
GET | /v4/users/me | Get the user for the current token |
GET | /v4/users/identify | Find a user by email or phone |
GET | /v4/users/batch_identify | Find several users by email or phone |
Fields
| Field | Read/write | Meaning |
|---|---|---|
id | read-only | Unique identifier. Example: usr_123abc. |
first_name | writable | Given name. Example: "Ada". |
last_name | writable | Family name. Example: "Lovelace". |
email | writable | Email address. Example: "ada@example.com". |
phone_number | writable | Phone number, ideally E.164. Example: "+15551234567". |
time_zone | writable | IANA time zone. Example: "America/New_York". |
suspended | writable | Whether the account is suspended. |
address | writable (create/update) | The user's address. On create/update you may pass a nested address object; in responses it's a reference you can expand[]. |
profile | writable (create/update) | Nested profile object (demographics, subscriptions, clothing sizes, etc.); a reference in responses. |
professional_experience | reference | Work history. expand[] to inline. |
education | reference | Education history. expand[] to inline. |
social_profiles | reference (array) | Linked social accounts. expand[] to inline. |
identifiers | reference (array) | The emails/phones this user is known by. expand[] to inline. |
challenge_submissions | reference (array) | Challenge entries. expand[] to inline. |
created_at / updated_at | read-only | Unix timestamps. |
When you create or update a user, address accepts a nested address object
(same fields as Addresses below) and profile accepts a nested
profile object with fields such as country_of_residence, gender, pronouns,
age, birthdate, dietary_preferences, clothing_sizes,
newsletter_subscriptions, and topic_subscriptions. In responses these come
back as references — add them with expand[] to see the full records.
Expanding and filtering
Expandable associations: professional_experience, education,
social_profiles, address, identifiers, challenge_submissions.
GET /v4/users supports filtering on id, first_name, and last_name (with
[eq], [contains], [excludes]) and email (with [eq], [contains]). See
Making requests.
GET /v4/users?first_name[contains]=ada&expand[]=address&limit=50
Special endpoints
GET /v4/users/me — returns the user tied to the current access token. Use it
to find out who you're acting as after signing a user in, without knowing their
id up front.
GET /v4/users/me
Authorization: Bearer <token>
GET /v4/users/identify — looks a user up by a single identifier (an email
address or a phone number) instead of by id. Use it when your system knows a
person's email or phone but not their MLH user ID.
identifier(required) — the email or phone to match. Example:ada@example.com.auto_create(optional, boolean) — if no user matches, create one instead of returning nothing.
GET /v4/users/identify?identifier=ada@example.com&auto_create=true
GET /v4/users/batch_identify — the same idea for many people at once. Pass
identifiers as an array of emails/phones; auto_create (optional) creates any
that don't yet exist. Use it to reconcile a whole list of contacts in one call.
GET /v4/users/batch_identify?identifiers[]=ada@example.com&identifiers[]=+15551234567
Addresses
A user's postal addresses, nested under the user. A user can have more than one
(each has a nickname, e.g. "Home" or "School").
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v4/users/{user_id}/addresses | List a user's addresses |
POST | /v4/users/{user_id}/addresses | Create an address |
POST | /v4/users/{user_id}/addresses/upsert | Create or update an address |
Fields
| Field | Read/write | Meaning |
|---|---|---|
id | read-only | Address identifier. |
nickname | writable | Label for the address. Example: "Home". |
line1 | writable | Street address. Example: "1 Main St". |
line2 / line3 | writable | Extra address lines (apt, suite). |
city | writable | Example: "New York". |
state | writable | State/province. Example: "NY". |
postal_code | writable | ZIP/postal code. Example: "10001". |
country | writable | Country name. Example: "United States". |
country_code | writable | ISO country code. Example: "US". |
created_at / updated_at | read-only | Unix timestamps. |
Special endpoint — upsert
POST /v4/users/{user_id}/addresses/upsert creates the address if it doesn't
exist yet and updates it if it does. Use this when you sync addresses from another
system and don't want to check first whether one already exists. If you include an
id (or a matching nickname) it updates that record; otherwise it creates a new
one.
POST /v4/users/usr_123abc/addresses/upsert
Content-Type: application/json
{
"nickname": "Home",
"line1": "1 Main St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country_code": "US"
}
Participations
Read-only list of the events a user has taken part in, from this user's side. To create or change a participation, use the Participations resource — this nested list is for reading only.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v4/users/{user_id}/participations | List a user's participations |
Fields
| Field | Meaning |
|---|---|
id | Participation identifier. |
status | One of applied, rejected, accepted, registered, checked_in, canceled. |
first_name / last_name | Name on the participation. |
email / phone_number | Contact on the participation. |
source | How it was created: ohq, manual_import, csvifier, flatfile, avo_upload, csv_upload. |
custom_fields | Event-specific extra data (object). |
user | The user (reference; expand[] to inline). |
event | The event (reference; expand[] to inline). |
created_at / updated_at | Unix timestamps. |
Expandable associations: event, user.
GET /v4/users/usr_123abc/participations?expand[]=event
Organized Events
Read-only list of the events a user organizes (as lead organizer, organizer, or staff). These are the same event records described in the Events resource, scoped to this user.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v4/users/{user_id}/organized_events | List events this user organizes |
Fields
Each item is an event. Key fields include id, slug, name, status
(draft, pending, in_progress, ended, canceled), event_format
(hackathon, hackday, presentation, workshop, conference, meetup,
evergreen, campaigns, hackweek), private, starts_at / ends_at (Unix
timestamps), time_zone, the various URLs (website_url, registration_url,
chat_url, submission_url), counts (registrations_count, check_ins_count,
project_submissions_count), and references such as lead_organizer,
organizers, sponsors, sponsorships, series, event_application, and
prize_categories. created_at / updated_at are Unix timestamps.
Expandable associations: prize_categories, series, sponsors, organizers,
lead_organizer, sponsorships, event_application.
GET /v4/users/usr_123abc/organized_events?expand[]=lead_organizer&expand[]=series
Phone Verifications
Reads the phone verification status for a user — a single record per user rather than a list.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v4/users/{user_id}/phone_verification | Get the user's phone verification |
Use this to check whether a user's phone number has been verified before relying
on it (for example, for SMS-based flows). It accepts expand[] to inline related
records. There's no create/update endpoint here — verification records are managed
by MLH.
GET /v4/users/usr_123abc/phone_verification
Projects
The projects a user has built, nested under the user. Use these endpoints to read a user's projects and to create or update them via upsert. For the standalone project resource, see Projects.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v4/users/{user_id}/projects | List a user's projects |
PUT | /v4/users/{user_id}/projects | Create or update a project (upsert) |
POST | /v4/users/{user_id}/projects | Create or update a project (upsert) |
Fields
| Field | Read/write | Meaning |
|---|---|---|
id | read-only | Project identifier. Include it to update an existing project. |
name | writable | Project name. Example: "Route Finder". |
description | writable | What the project does. |
source_url | writable | Link to the code. Example: "https://github.com/ada/route-finder". |
demo_live_url | writable | Link to a live demo. |
demo_video_url | writable | Link to a demo video. |
built_with | writable (array) | Technologies used. Example: ["python", "react"]. |
team_member_count | writable | Number of people on the team. Example: 3. |
custom_fields | writable (object) | Extra structured data. |
project_submissions | reference (array) | Submissions to events. expand[] to inline. |
project_users | reference (array) | Team members. expand[] to inline. |
challenge_submission | reference | Linked challenge entry. expand[] to inline. |
screenshots | reference (array) | Attached images. expand[] to inline. |
created_at / updated_at | read-only | Unix timestamps. |
Expandable associations: project_submissions, project_users,
challenge_submission, screenshots.
Special endpoint — upsert
Both PUT and POST /v4/users/{user_id}/projects upsert: they create the
project if it's new and update it if it already exists. Include an id in the
body to target an existing project; omit it to create one. The two verbs behave
the same way here — use whichever fits your client.
PUT /v4/users/usr_123abc/projects
Content-Type: application/json
{
"name": "Route Finder",
"description": "Finds the fastest route across campus.",
"source_url": "https://github.com/ada/route-finder",
"built_with": ["python", "react"],
"team_member_count": 3
}
Gotchas
- Sub-resources are nested under the user. Addresses, participations, organized
events, phone verification, and projects all live under
/v4/users/{user_id}/.... You need the user'sidfirst — get it fromGET /v4/users/me,GET /v4/users/identify, or a list query. - Upsert means create-or-update. For addresses and projects, pass an
idto update an existing record or omit it to create one. Don't assume upsert always creates a new row. - Participations and organized events here are read-only. Manage participations through the Participations resource.
- References vs. inlined data. Associations come back as references by default;
add
expand[]to inline them. See Expanding related data.