Authentication
Meetup uses access tokens to authenticate GraphQL requests. If you do not have a token or if your token is expired, please go to OAuth2 Server Flow to create or manage your tokens.
To authenticate the requests, set the Authorization HTTP header in your requests.
Header | Value |
---|---|
Authorization | Bearer {YOUR_TOKEN} |
To verify that your token is working, run the following command in the terminal:
query='query { self { id name } }'
curl -X POST https://api.meetup.com/gql-ext \
-H 'Authorization: Bearer {YOUR_TOKEN}' \
-H 'Content-Type: application/json' \
-d @- <<EOF
{"query": "$query"}
EOF
You should see a response similar to the example below if your token works correctly. If not, please go to OAuth2 Server Flow to create or refresh your token.
{
"data":{
"self":{
"id":1234,
"name":"Cool Developer"
}
}
}
Querying with GraphQL
GraphQL queries return JSON with only the fields you specify. For example, the following invokes the event query with the eventId as an argument. The query returns the title, description, and date from an event.
Example: Basic query
Query
query {
event(id: "276754274") {
title
description
dateTime
}
}
Response
{
"data": {
"event": {
"title": "API test",
"description": "API test description",
"dateTime": "2021-03-15T09:52:53+09:00"
}
}
}
Example: Variables
Variables simplify GraphQL queries and mutations by letting you pass data like you do for arguments to a function. Variables begin with $ and are defined in JSON format.
Basic example with variables
query ($eventId: ID) {
event(id: $eventId) {
title
description
dateTime
}
}
Variables
{ "eventId": "276754274" }
Equivalent curl command
query='query($eventId: ID!) {
event(id: $eventId) {
title
description
dateTime
}
}
'
variables='{
"eventId": "276754274"
}'
query="$(echo $query)"
curl -X POST https://api.meetup.com/gql-ext \
-H 'Authorization: Bearer {YOUR_TOKEN}' \
-H 'Content-Type: application/json' \
-d @- <<EOF
{"query": "$query", "variables": "$variables"}
EOF
Example: Pagination
Retrieving large collections of data takes a long time and is resource intensive. Because of this, we recommend using pagination. Pagination is the process of dividing the records in results into smaller sets or pages. Meetup GraphQL uses cursor-based pagination.
Cursor-based pagination works by returning an identifier or cursor to a specific record in the dataset. Meetup GraphQL uses the endCursor field in the pageInfo object as the reference to the last record in the page. On subsequent requests, passing along the endCursor value and size as arguments tells GraphQL to return results after the given cursor.
First, let’s get the first 3 groups of Pro Network and get the endCursor of the pageInfo object. The default page size is 20, so to get the first 3 you need to specify it in the first argument.
Query
query ($urlname: ID) {
proNetwork(urlname: $urlname) {
groupsSearch(input: {first: 3}) {
totalCount
pageInfo {
endCursor
}
edges {
node {
id
name
}
}
}
}
}
Variables
{ "urlname": "YOUR_NETWORK_URLNAME" }
Response
{
"data": {
"proNetwork": {
"groupsSearch": {
"totalCount": 15,
"pageInfo": {
"endCursor": "[['1524147138299'],['28214336']]"
},
"edges": [
{
"node": {
"id": "33662217",
"name": "Kharkiv Adventure Meetup Group"
}
},
{
"node": {
"id": "29968188",
"name": "L0t$ of $pecial Ch@r@cters *.* đź‘»"
}
},
{
"node": {
"id": "28214336",
"name": "Testing it out"
}
}
]
}
}
}
}
To fetch the next set of records, copy the endCursor value and use it as after the argument value. The following example includes both fields and uses query variables to pass their values as arguments. The $itemsNum variable is required, and is used to specify the number of results to return.
Query
query ($urlname: ID!, $itemsNum: Int!, $cursor: String!) {
proNetwork(urlname: $urlname) {
groupsSearch(input: {first: $itemsNum, after: $cursor}) {
totalCount
pageInfo {
endCursor
}
edges {
node {
id
name
}
}
}
}
}
Variables
{
"urlname": "YOUR_NETWORK_URLNAME",
"itemsNum": 3,
"cursor": "[['1524147138299'],['28214336']]"
}
Example: Collecting a list of groups in your network
To get a list of all groups from your network, first you need to get a Pro Network by ID or by urlname. Then, use the groupsSearch subquery.
Query
query($urlname: ID!) {
proNetwork(urlname: $urlname) {
groupsSearch(input: { first: 2, filter: {} }) {
totalCount
pageInfo {
endCursor
}
edges {
node {
id
name
urlname
memberships {
totalCount
}
}
}
}
}
}
Variables
{ "urlname": "YOUR_NETWORK_URLNAME" }
Response
{
"data": {
"proNetwork": {
"groupsSearch": {
"totalCount": 15,
"pageInfo": {
"endCursor": "[['1524147138299'],['28214336']]"
},
"edges": [
{
"node": {
"id": "33662217",
"name": "Kharkiv Adventure Meetup Group",
"urlname": "Kharkiv-Adventure-Meetup-Group",
"memberships": {
"totalCount": 4
}
}
},
{
"node": {
"id": "28214336",
"name": "Testing it out",
"urlname": "meetup-pro-test-group",
"memberships": {
"totalCount": 34
}
}
}
]
}
}
}
In addition, you can search groups by different filter params which can be added to the input object
Example: Collecting list of upcoming events from the Pro Network
You can filter for upcoming events by calling the eventsSearch subquery and setting the status filter.
Query
query($urlname: ID!) {
proNetwork(urlname: $urlname) {
eventsSearch(input: { first: 3, filter: { status: "UPCOMING" } }) {
totalCount
pageInfo {
endCursor
}
edges {
node {
id
title
}
}
}
}
}
Variables
{ "urlname": "YOUR_NETWORK_URLNAME" }
Response
{
"data": {
"proNetwork": {
"eventsSearch": {
"totalCount": 15,
"pageInfo": {
"endCursor": "[['1524147138299'],['28214336']]"
},
"edges": [
{
"node": {
"id": "33662217",
"title": "Kharkiv Adventure Meetup"
}
},
{
"node": {
"id": "28214336",
"title": "Testing it out"
}
}
]
}
}
}
}
In addition, you can search groups by different filter params which can be added to the input object
Example: Collecting event information
Use the event query and filter to fetch Event details.
Query
query($eventId: ID!) {
event(id: $eventId) {
title
eventUrl
description
dateTime
duration
eventHosts {
memberId
name
}
featuredEventPhoto {
id
baseUrl
}
group {
id
name
urlname
}
rsvps {
edges {
node {
id
member {
name
}
}
}
}
}
}
Variables
{
"$eventId": "EVENT_ID"
}
Response
{
"data": {
"event": {
"title": "Testing API things 1",
"eventUrl": "https://www.meetup.com/whiskey-wednesdays/events/305912103/",
"description": "",
"dateTime": "2025-02-19T19:00:00-07:00",
"duration": "PT0S",
"eventHosts": [
{
"memberId": "246752233",
"name": "Test"
}
],
"featuredEventPhoto": {
"id": "495693322",
"baseUrl": "https://secure-content.meetupstatic.com/images/classic-events/"
},
"group": {
"id": "6622782",
"name": "Whiskey Wednesdays",
"urlname": "whiskey-wednesdays"
},
"rsvps": {
"edges": [
{
"node": {
"id": "1870061534!chp",
"member": {
"name": "Vadim Vlasenko (test)"
}
}
}
]
}
}
}
}
In addition, you can get other fields from the Event object
Example: Collecting RSVP information
To fetch RSVP information for upcoming events, you first call the eventSearch query to get the list of upcoming events. Then, retrieve their RSVP details.
Query
query($urlname: ID!) {
proNetwork(urlname: $urlname) {
eventsSearch(input: { first: 3, filter: { status: "UPCOMING" } }) {
totalCount
pageInfo {
endCursor
}
edges {
node {
id
rsvps {
edges {
node {
id
member {
name
email
}
}
}
}
}
}
}
}
}
Variables
{
"urlname": "YOUR_NETWORK_URLNAME"
}
Response
{
"data": {
"proNetwork": {
"eventsSearch": {
"totalCount": 12,
"pageInfo": {
"endCursor": "[['1654383600000'],['264247573']]"
},
"edges": [
{
"node": {
"id": "278461755",
"rsvps": {
"edges": [
{
"node": {
"id": "1874780639!chp",
"member": {
"name": "test test (test)",
"email": "email@meetup.com"
}
}
}
]
}
}
},
{
"node": {
"id": "278450119",
"rsvps": {
"edges": [
{
"node": {
"id": "1874730763!chp",
"member": {
"name": "test test",
"email": "email@meetup.org"
}
}
}
]
}
}
},
{
"node": {
"id": "278000838",
"rsvps": {
"edges": [
{
"node": {
"id": "1872396589!chp",
"member": {
"name": "test test",
"email": "email@meetup.com"
}
}
}
]
}
}
}
]
}
}
}
}
You can check other fields from the Rsvp object if you need
Example: Collecting RSVP registration answers
RSVP registration answers for a given event can be retrieved by using the eventRegistrationAnswers query.
Query
query($urlname: ID!, $eventId: ID!) {
proNetwork(urlname: $urlname) {
eventRegistrationAnswers(input: { filter: { eventIds: [$eventId]}}) {
totalCount
edges {
node {
answers {
question
answer
}
}
}
}
}
}
Variables
{
"urlname": "YOUR_NETWORK_URLNAME"
"$eventId": "EVENT_ID"
}
Response
{
"data": {
"proNetwork": {
"eventRegistrationAnswers": {
"totalCount": 1,
"edges": [
"answers": [
{
"question": "Some question",
"answer": "Some answer"
},
{
"question": "Another question",
"answer": "Another answer"
}
]
]
}
}
}
}
Publishing with GraphQL
While GraphQL queries are used to fetch data, mutations modify records. They can be used to insert, update, or delete data.
Example: Publishing a new event
Events are typically drafted and reviewed before being published for the community to see. This example shows how to create a draft and publish the event. Please check the input schema for more detailed information.
Mutation
mutation($input: CreateEventInput!) {
createEvent(input: $input) {
event {
id
}
errors {
message
code
field
}
}
}
Variables
{
"input": {
"groupUrlname": "GROUP_URLNAME",
"title": "EVENT_TITLE",
"description": "EVENT_DESCRIPTION",
"startDateTime": "EVENT_STARTTIME",
"venueId": "EVENT_VENUE_ID",
"duration": "EVENT_DURATION",
"publishStatus": "DRAFT"
}
}
Response
{
"data": {
"createEvent": {
"event": {
"id": "278472065"
},
"errors": []
}
}
}
Once you have the event drafted and ready to be published, run the following mutation
Mutation
mutation($input: EditEventInput!) {
editEvent(input: $input) {
event {
id
}
errors {
message
code
field
}
}
}
Variables
{
"input": {
"eventId": "EVENT_ID",
"publishStatus": "PUBLISHED"
}
}
Response
{
"data": {
"editEvent": {
"event": {
"id": "278472065"
},
"errors": []
}
}
}
Example: Uploading an image
Uploading images consists of 2 parts. Creating a placeholder with a file name and then uploading the binary to our system.
First, create a placeholder for the image in the system by invoking the createGroupEventPhoto
mutation.
Mutation
mutation($input: GroupEventPhotoCreateInput!) {
createGroupEventPhoto(input: $input) {
uploadUrl
photo {
id
baseUrl
}
imagePath
}
}
Variables
{
"input": {
"groupId": "GROUP_ID",
"photoType": "GROUP_PHOTO",
"contentType": "JPEG",
"setAsMain": false
}
}
Response
{
"data": {
"uploadImage": {
"uploadUrl": "{upload link}",
"photo": {
"id": "496557439",
"baseUrl": "https://secure-content.meetupstatic.com/images/classic-events/",
},
"imagePath": ""
}
}
}
The response contains an uploadUrl
where you can upload the image content-type using Postman, curl, or other similar application. The ID
value can be used as reference to other queries or in other mutations such as modifying an event.
Example: Editing an event
Please check the input object description for other fields which can be edited.
Mutation
mutation($input: EditEventInput!) {
editEvent(input: $input) {
event {
id
}
errors {
message
code
field
}
}
}
Variables
{
"input": {
"eventId": "EVENT_ID",
"description": "EVENT_DESCRIPTION",
"howToFindUs": ”EVENT_LOCATION_HINT”,
"featuredPhotoId": PHOTO_ID
}
}
Response
{
"data": {
"editEvent": {
"event": {
"id": "278472065"
},
"errors": []
}
}
}
Working with Photos
The Meetup API provides access to the various photos associated with members, events, and groups. You can access these photos with the PhotoInfo object. The PhotoInfo object provides you with the flexibility to choose the size and type of image you would like to display. We currently support JPG and WEBP image formats.
Example: Event photo
Query
query($eventId: ID!) {
event(id: $eventId) {
id
featuredEventPhoto {
id
baseUrl
}
}
}
Variables
{ "eventId": "283013941" }
Response
{
"data": {
"event": {
"id": "283013941",
"featuredEventPhoto": {
"id": "501175080",
"baseUrl": "https://secure-content.meetupstatic.com/images/classic-events/"
}
}
}
}
In the above example, you can access the featured event photo as the first image in the images array. You will receive a list of PhotoInfo objects in the array. Then you can concatenate the baseUrl and Id of any given image with the desired width, height, and type of the image you desire.
As an example, here are two variations of valid photo links from the above response:
Rate Limiting
The Meetup API aims to provide consistent responsiveness and equal quality of service for all its consumers. In order to do so, we limit the frequency at which the API will produce successful responses to a single client.
The API currently allows you to have 500 points in your queries every 60 seconds. Clients that issue too many requests in a short period of time will receive an error message in their response. The error will display which query has caused you to be rate-limited along with the time when your limit will be reset.
{
"errors": [
{
"message": "Too many requests, please try again shortly.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"event"
],
"extensions": {
"code": "RATE_LIMITED",
"consumedPoints": 500,
"resetAt": "2021-12-12T18:37:51.644Z"
}
}
],
"data": null
}
This response indicates that you are making requests too quickly. If you receive one of these errors, you should adjust the frequency of your requests by adding a pause between them.
Meetup API migration guide
In February 2025, we released a new version of Meetup’s GraphQL API.
With this change, the Meetup API will support full schema introspection, enabling any customer to use any GraphQL tool to explore the schema seamlessly.
However, it’s possible that previous API configurations may break as a result of these changes. This is largely a result of us renaming certain data fields. This guide is intended to help you easily adapt your existing integration to take advantage of the new and improved API and avoid downtime.
We invite you to perform the following steps:
- 1. Update the API URL to the new one:
https://api.meetup.com/gql-ext
- 2. Validate a query; Since the introspection is enabled for the new API, any query can be easily validated against the new API by using any GraphQL tool.
- 3. If a query is invalid, identify specific issues and review the major changes listed below.
- 4. Сontact support if you continue to experience issues.
Release notes
General changes
Old | Renamed to / replaced by | Moved to |
---|---|---|
Ticket | RSVP | |
User | Member | |
GroupMembership | Membership | |
Image | Photo | |
keywordSearch | Split into: groupSearch, eventSearch | |
proNetworkByUrlname | proNetwork | |
uploadImage | createGroupEventPhoto | |
count | totalCount |
Event type changes
Old | Renamed to / replaced by | Moved to |
---|---|---|
image/images | featuredEventPhoto | |
isOnline | eventType The Event.type field is much more powerful as it now supports online, inPerson, and hybrid as well. | |
going, waiting, tickets | rsvps (with filters applied) | |
attendingTicket, ticket | rsvp |
Member type changes
Old | Renamed to / replaced by | Moved to |
---|---|---|
tickets | rsvps | |
hostedEvents, upcomingEvents, draftEvents | memberEvents (with filters applied) | |
recommendedGroups | Query.recommendedGroups |
Membership type changes
Old | Renamed to / replaced by | Moved to |
---|---|---|
mostRecentVisitDate | lastAccessTime | |
joinedDate | joinTime | |
noShowCount | RsvpStats.noShowCount |
Group type changes
Old | Renamed to / replaced by | Moved to |
---|---|---|
logo | keyGroupPhoto | |
latitude | lat | |
longitude | lon | |
topics | activeTopics | |
membershipSearch | memberships | |
draftEvents, pastEvents, upcomingEvents | events (with filters applied) |
ProNetwork type changes
Old | Renamed to / replaced by | Moved to |
---|---|---|
isMemberEmailShared | isProEmailShared | |
rsvpSurveys | eventRegistrationAnswers |
RSVP type changes
Old | Renamed to / replaced by | Moved to |
---|---|---|
user | member | |
quantity | Removed | |
updatedAt | updated |