GET Protocol

Welcome to the GET Protocol API-documentation.

Versioning ¶ This is v1.0 ¶ This is the stable version. Specifying Versions: ¶ Version is specified by in url the format is: api/v / . All url used below use the version of this document. Versions are in the format X.X If a version is invalid or unsupported it will return a 404. Currently Supported version ¶ Version 1.0 (Stable)

Getting Started ¶ Logging in as a customer ¶ First, Login by requesting an access code for +31612345678 curl '' https: { "authid" : "<auth_id>" , "tries_left" : 3 , "used" : false } The user will now receive a SMS containing the access code. The code is a 4 digit number, e.g. 1234. curl 'https://gus.api.sandbox.guts.events/api/v1/auth-verify/' -X POST '--data-raw ' { "pincode" : "8452" , "app" : "web" , "verification_code" :<authid>}' //Response { "status" : "succse" , "refresh_token" : "<refresh_token>" , "jwt_token" : "<jwt_token>" } The system has two kind of users: Customers and Dashboard users. Both users use JWT for authentication but they have different ways of obtaining them. Customers are identified by their mobile phone that they use to obtain a JWT token. Dashboard users use their username/password to authenticate. Both types of users use the same endpoints. The system is aware of the type of user and might differentiate the retrieved results depending on the user type. In general, Dashboard users will see more information. When logging in the api returns a refresh token and a jwt token. The jwt token is used for authentication. It needs to be added to the header of all endpoints that require authentication. Authorization : JWT <token> The jwt token is valid for 15 minutes. When it expires the refresh token can be used to retrieve a new jwt token. Post to the refresh endpoint and it will return a new jwt token AND refresh token. Use the jwt token to authenticate and store the new refresh token to retrieve the next jwt token. When unused, refresh tokens are valid for six months. To keep your app logged you’ll need to store the refresh token. Check the authentication section for more details. Pagination ¶ All listing endpoints use pagination. It follows this format: { " count ": 1 , " next ": url , " previous ": url , " results ": .... } Error handling ¶ The API will raise HTTP error codes when it receives malformed or incorrect input. There are cases where the API request was valid and well formed but some logic restriction prevents the call API call from completing successfully. Example: Trying to put a ticket in your cart when no tickets are available. In these cases the call will return a 200 but will provide a status in the payload. { " status ": "error|success" , " error ": "error_code_string" }

Use Case: Getting Shop Access ¶ Our ticketing platform has to deal with a high service load associated with ticket sales. Therefore the system makes use of a gatekeeper. This gatekeeper can throttle access to the ticketing service to prevent it from getting bogged down under high load. For users to buy tickets at the ticketing service, they first need to retrieve coupons at the gatekeeper. The ticketservice will refuse to start a payment with the user without a coupon. If the load is low, the gatekeeper will immediately give users a coupon; if the load gets high, the gatekeeper will start a waitingline and hand out coupons accordingly. Getting events information ¶ To get started you need the gate_slug. It is suggested that the slug is part of the url your are using to let you users buy tickets. Using the gate_slug, you retrieve some information about the event and its shop curl 'https://gate.api.sandbox.guts.events/api/v1.0/gates/<gate_slug>' // Response { "id" : 423 , "slug" : "jy0iox" , "human_slug" : "" , "title" : "title" , "subtitle" : "subtitle" , "image" : "url" , "shops" :[ 320 ], "shop_slugs" :[ "bnbgpz" ], "start" : "2019-05-14T08:00:00Z" , "auto_queue_started" : "0001-01-01T00:00:00Z" , "type" : "open" , "state" : "running" , "message" : "" , "entries_per_minute" : 0 , "time_to_start" :- 17818393 } And for shops information. curl 'https : { [ "id" : 320 , "slug" : "bnbgpz" , "event_slug" : "xdzf0q" , "title" : "" , "subtitle" : "" , "location" : "" , "local_timezone" : "Europe/Amsterdam" , "city" : "" , "when" : "2019-12-17T19:00:00Z" , "time_till_soldout" : "2019-11-15T11:37:24.761296Z" , "state" : "no_available" , "publish_state" : "published" ] } Note that a gate can contain multiple shops The most important part is the shop_slug and event_slug: you’ll need these to access the correct resources later when using the ticketing service. But first the user needs to retrieve a coupon. Getting a Coupon ¶ First get a handle to join the waitingline. curl 'https : { "status" : "success" , "handle" : "6aa8efbf737905bbe65a015b89c2982a" } The gatekeeper has now added the user to the queue. Based on load the gate will advance users within the queue. To check the users status. curl 'https: { "handle" : "6aa8efbf737905bbe65a015b89c2982a" , "gate" : "jy0iox" , "number" : "" , "email" : "" , "entered" : "2019-12-06T13:33:14.435384374Z" , "position" : 1 , "last_advanced" : 0 , "minutes_till_turn" : 0.3333333333333333 , "state" : "waiting" |" turn ", "message" : "" } You’re supposed to poll this endpoint until the state has changed from waiting to turn . When the state is turn your can retrieve the coupon. curl 'https : { "status" : "success" , "coupon" : "Omc0MjM6MDo2YWE4ZWZiZjczNzkwNWJiZTY1YTAxNWI4OWMyOTgyYTo4MDkzOWRmZGZlNDg2ZGQyNDU3Zjk4OTQ5YmQzNzFkMw==" } Redeeming the Coupon ¶ The coupon should be added to the request header when you call the shop endpoints. The user needs to be logged in for the coupon to be processed. curl 'https://api.sandbox.guts.tickets/api/v1/shops/<shop_slug>/' -H 'coupon: coupon_hash' -H 'Authorization: JWT jwt_token If the coupon is correctly processed, the response header will contain the header coupon:processed . The following endpoints will process coupons: https://api.sandbox.guts.tickets/api/v1/shops/<shop_slug>/

https://api.sandbox.guts.tickets/api/v1/events/<event_slug>/

https://api.sandbox.guts.tickets/api/v1/carts/<shop_slug>/<uuid>/checkout/ In a normal flow, the coupon will be processed during the cart checkout because it’s the first time the user will be authenticated. The shop and event endpoint process coupons to provide a good workflow for already logged in users. A coupon can only be used once. After it has been processed it will be ignored.

Use Case: Buying a ticket flow ¶ Before you can buy a tickets make sure you first complete the gatekeeper flow to obtain the shop_slug and a coupon . Make sure you add the coupon to your request header as described. Getting the shop details ¶ curl 'https://api.sandbox.guts.tickets/api/v1.0/shops/<shop_slug>/' { "id" : 1 , "slug" : "pkdcbk" , ... "ticket_kinds" : [ { ... "id" : <id_1>, ... }, { ... "id" : <id_2>, ... } Create a cart ¶ Next we need to create a cart. You’ll need to create a uuid4 for the cart yourself the format is example: 98 bebab2- 3969 - 4 ec4-b16c-dcabfc4e9c5f Next we retrieve the cart. curl 'https : { "uuid" : "<uuid>" , "ticket_kinds" : [] } Fill the cart ¶ As you can see the cart is now empty. We can fill the cart with tickets by sending a PUT request with this data payload structure. The kind key is the ID of the ticket kind that the user selects. These IDs are available in the event detail response under the key ticket_kinds . Make sure you always send the whole ticket/rank state. { " ticket_kinds ": [ { " id ": <kind_1> , " amount ": 2 }, { " id ": <kind_2> , " amount ": 1 } ] } curl 'https://api.sandbox.guts.tickets/api/v1.0/carts/<shop_slug>/<uuid>' -X PUT -H 'Content-Type: application/json; charset=utf-8' --data '{" ticket_kinds ": [{" id ": 1, " amount ":0},{" id ": 2, " amount ":1}]}' Payment ¶ To start a payment we first have to checkout the cart. At this point the user has to be logged in. So make sure to authenticate the user using the correct Authorization header (see example). curl 'https://api.sandbox.guts.tickets/api/v1.0/carts/ <shop_slug> / <uuid> /checkout/' -X PUT -H 'Authorization: JWT <jwt_token> ' //Response { "order" : <id> , "state" : "" } Using the returned order we can start a payment on the order. curl 'https://api.sandbox.guts.tickets/api/v1.0/orders/ <order_id> /start_payment/' -X POST -H 'Authorization: JWT <jwt_token> -H 'Content-Type: application/json; charset=utf-8' --data '{ "psp_arguments" , { "return_page" : <your_url> }}' //Response { "status" : "redirect" : <url> } The return_url should be the url of your landing page that handles the order completion. After this list your new tickets. curl 'https://api.sandbox.guts.tickets/api/v1.0/tickets/' -H 'Authorization: JWT <jwt_token>'

Use Case: Retrieving QR code ¶ To scan a users ticket you’ll will need retrieve the QR code. QR code are not per ticket but per event. The scanner knows which tickets belong to which user. So each user has only one QR code for all tickets of the same event. Getting the code ¶ curl 'https : { "code" : <qrcode>, The code should be rendered unchanged as a standard qrcode. The code itself is valid for 15 seconds. So a new one should be retrieved every 15 seconds

Use Case: External Authentication ¶ The GET Protocol only allows user to login with a mobile phone using an sms. If you want create a different login flow (email and password for example) you’ll have to create your own login flow. After the user has logged into your system your can request authentication as a ticketeer token on behalf of a user. You can then pass along this token to your user. This will create a separate user in our system that can only be accessed by your ticketeer

This requires an api key specific to your ticketeer. User hash ¶ You’ll be identifying your user by a hash. This can be anything, it’s up to you. We suggest to build the hash based on how you identify your users. This is has two benefits: You don’t have to keep track of some sort of id. You can build the hash on the fly based on the user’s credentials

It allows you to keep all personal information outside of the GET Protocol Login external user ¶ An example using python3 to generate a hash >>> import hashlib >>> h = hashlib.sha256() >>> h.update( b"username@mail.com" ) >>> h.hexdigest() '5ef7609155bc0a9f37d1a149e6d7dcf63d2bb841ecdad3667e7149e5dfff9df1' curl -X POST --data '{" user_hash ":" 5 ef7609155bc0a9f37d1a149e6d7dcf63d2bb841ecdad3667e7149e5dfff9df1 "," app ":" mobile "}' https: Response { "status" : "success" , "token" : "jwt_token" , "refresh_token" : "refresh_token" }

Use Case: External Ticketeer ¶ Integrate an external system that handles the sales and seating with GUTS. A user can see his tickets in the GUTS ticket wallet app and scan his ticket with GUTS scanner. Authentication ¶ A dashboard user account is required to continue the procedure. Contact GUTS to learn more and have your account generated. Check here how to login and here how to use the tokens. Get organization ID ¶ You need to know your organization id if you want to create an event. Check here on how to retrieve it. Normally you are going to be part of one organization, so the list will contain only one item, which will be used to create the event. We are going to use the key id of that item for the next steps and refer to it as organization_id. Create event ¶ Check here how to create an event. You probably want to use hide: true and organization: <organization_id> from the previous step. We refer to the returned id as event_id. (Optional) Upload cover photo for event ¶ In order to set the cover photo for an event you need to: Request upload url from GET Protocol, check here how to do that, setting field cover. After that you need to upload the image to the amazon s3 bucket using the key returned by request upload url. const s3Object = new FormData(); Object .keys(data.fields).forEach((k) => { s3Object.append(k, data.fields[k]); }); s3Object.append( 'Content-Type' , image.type); s3Object.append( 'file' , image); axios.post(data.url, s3Object).then(() => { const url = data.url + data.fields.key }) Use the resulting key as cover in event edit api. Create shop ¶ Check here how to create a shop. We refer to the id of the object returned as shop_id Create privilege type ¶ Check here how to create a PrivilegeType. In the case of external ticketeer the recommended payload is: { "name":"My Ticket", "total":"0", "blocks":0, "upsell":false, "event": "<event_id>" // from previous step }' We refer to the id of the object returned as privilegetype_id Create ticket kind ¶ Check here how to create a ticket-kind. In the case of external ticketeer the recommended payload is: { "shops" : [ "<shop_id>" ], "name" : "My ticket kind" , "privilege_types" : [ "privilegetype_id" ] "description" : "My description" , "price_buildup" : [{ "tag" : "ticket" , "vat" : 0 , "price" : "34.00" , "is_base" : true }], "for_sale" : false , "guest" : true } We refer to the id of the object returned as ticketkind_id Publish Event ¶ Check here Register Ticket ¶ You need to have already obtained the user’s mobile phone in your system. Note: normalizing mobile phone is not required. +31611111111 and 0611111111 are both valid. After a successful ticket sale in your system, register ticket in GET Protocol. Check here how to make the request. You need to use ticketkind_id from previous step as ticket_kind. You need to use “pool”: “new” to create new tickets instead of using the ones that are for sale. If you followed the steps above, the ticketkind does not have any available tickets for sale. You need to use “paid_to”: “eo” meaning that the ticket was already paid to the Event organizer without going through GET Protocol backend. You need to use “free”: false" meaning that it is not a gift. The response contains an array of tickets, and each of them contains an array of privileges (in this case all of them are of length one). We are going to refer to the ids of this single item of the privileges array as privilege_id. We are going to refer to the ids of the tickets list as ticket_id Register Seating for a ticket ¶ Check here how to edit privileges. You need to use privilege_id from above and use info to add your seating details. This field will show up in the GUTS apps. In case of editing/changing tickets in your system and you want to keep GET Protocol in sync, you can invalidate tickets. You need to use ticket_id from previous step.

Authentication Endpoints ¶ Authentication and Users ¶ IMPORTANT Authentication for customers is on different domain then the rest of the application When a user must be logged in to a specific ticketeer. Only resources belonging to that ticketeer can be accessed else a 404 is returned. Customer request verification_code ¶ Customer request verification_code POST /api/v1/auth-ticketeer/{ticketeer}/{recipient} Example URI POST /api/v1/auth-ticketeer/ ticketeer / recipient URI Parameters Hide Show ticketeer string (required) name of the ticketeer logging into recipient string (required) phoneumber of recipient include contrycode Request Hide Show Headers Content-Type : application/json Body { " locale ": "en" } Schema { " type ": "object" , " properties ": { " locale ": { " type ": "string" "description" : "determines language used in SMS" } } , " required ": [ "locale" ] } Response 200 Hide Show Headers Content-Type : application/json Body { " verification_code ": "7b5601a3-4a25-4955-8568-4f703a219aa1" , " tries_left ": 3 , } Customer login ¶ Customer login POST /api/v1/auth-verify Example URI POST /api/v1/auth-verify Request Hide Show Headers Content-Type : application/json Body { " pincode ": "2834" , " verification_code ": "7b5601a3-4a25-4955-8568-4f703a219aa1" , " app ": "web" } Schema { " type ": "object" , " properties ": { " pincode ": { " type ": "string" "description" : "pin sent to users phone" } , " verification_code ": { " type ": "string" "description" : "id of verification_code" } "app" : { " type ": "string" "description" : "id of client used. This allow to be logged in to multiple devices" , " pattern ": "queue|mobile|wallet|shop|ios|android|web|default" } } , " required ": [ "pin" , "verification_code" , "app" ] } Response 200 Hide Show Headers Content-Type : application/json Body { " status " : "error" "error" : "invalid_pin" | "used_verification_code" "tries_left" : 2 , } Response 200 Hide Show Headers Content-Type : application/json Body { " status " : "success" "tries_left" : 2 , " token ": "jwt token" "refresh_token" : "refresh token" } Refresh Token ¶ Refresh Token POST /api/v1/refresh Use this endpoint to retrieve a new jwt token and refresh token. Example URI POST /api/v1/refresh Request Hide Show Headers Content-Type : application/json Body { " refresh_token ": "refresh token" , } Schema { " type ": "object" , " properties ": { " refresh_token ": { " type ": "string" "description" : "refresh token" } } , " required ": [ "refresh_token" ,] } Response 200 Hide Show Headers Content-Type : application/json Body { " status " : "error" "error" : "refresh_token_expired" | "refresh_token_not_found" } Response 200 Hide Show Headers Content-Type : application/json Body { " status " : "succes" "token" : "jwt token" "refresh_token" : "new refresh token" } External authentication ¶ External authentication POST /api/v1/external-auth Example URI POST /api/v1/external-auth Request Hide Show Headers Content-Type : application/json

x-api-key : ticketeer-token Body { "user_hash" : "hash" , "app" , "mobile" } Schema { " type ": "object" , " properties ": { " user_hash ": { " type ": "string" "description" : "hash to identify user" "pattern" : "^[a-zA-z0-9\-\/_\+=]+$" } , " app ": { " type ": "string" "description" : "id of client used. This allow to be logged in to multiple devices" , " pattern ": "queue|mobile|wallet|shop|ios|android|web|default" } } , " required ": [ "user_hash" , "app" ] } Response 200 Hide Show Headers Content-Type : application/json Body { " status " : "succes" "token" : "jwt token" "refresh_token" : "new refresh token" } Event organizer Login ¶ Event organizer Login POST /api/v1.0/api-token-auth/ Event organizers login to GET Protocol using their credentials instead of a mobile phone. Contact GET Protocol to get your credentials. Example URI POST /api/v1.0/api-token-auth/ Request Hide Show = { " username ": "MY USERNAME" , " password ": "MY PASSWORD" } Headers Content-Type : application/json Schema { " type ": "object" , " properties ": { " username ": { " type ": "string" "description" : "Username provided" } , " password ": { " type ": "string" "description" : "Your password" } } , " required ": [ "username" , "password" ] } Response 200 Hide Show Headers Content-Type : application/json Body { " id ": 62721 , " username ": "+3161345678" , " first_name ": "" , " last_name ": "" , " email ": "" , " verified ": false , " date_joined ": "2019-02-06T12:26:15.357093+01:00" , " profile_complete ": false , " gender ": null , " birthdate ": null , " zipcode ": "" , " house_number ": "" , " locale ": "nl" , " opt_in ": null , " avatar ": null , " can_share_tickets ": true , " refresh_token ": "refresh_token" , " jwt_token ": "jwt_token" } JWT Token Refresh ¶ JWT Token Refresh POST /api/v1.0/api-token-refresh/ Eventually the jwt token will expire. A new one can be retrieved using the refresh token. If a new refresh token is generated the old one will become invalid.(logging in on a different device) Example URI POST /api/v1.0/api-token-refresh/ Request Hide Show Headers Content-Type : application/json Body { " client_id ": "web" , " refresh_token ": "refresh_token" } Schema { "type": "object", "properties": { "client_id": { "type": "string", "description": "can be anything, allows to be signed in multiple devices" }, "refresh_token": { "type": "string", "description": "determines language used in SMS" } }, "required": ["client_id", "refresh_token"] } Response 201 Hide Show Headers Content-Type : application/json Body { " token ": "jwt_token" , " refresh_token ": "refresh_token" } Self ¶ Self GET /api/v1.0/users/self Return the user that makes the request. The user is located from the information in the JWT token in the header. Example URI GET /api/v1.0/users/self Request Hide Show Headers Content-Type : application/json

Authorization : JWT <token> Response 200 Hide Show Headers Content-Type : application/json Body { " id ": 1 , " username ": "+3161234568" , " first_name ": "" , " last_name ": "" , " email ": "email@gmail.com" , " verified ": false , " is_active ": true , " date_joined ": "2018-04-03T11:15:55+02:00" , " profile_complete ": true , " gender ": "o" , " birthdate ": "1989-01-02T01:00:00+01:00" , " zipcode ": "" , " house_number ": "" , " locale ": "en" , " opt_in ": "true" , }

Gatekeeper Endpoints ¶ Gatekeeper ¶ IMPORTANT the gate keeper is on different domain then the rest of the application To buy tickets you need the slug of the gate to a specific shop. Note multiple gates can point to the same shop and a gate can point to multiple shops. Gate Detail GET /api/v1.0/gates/{gate_slug} Example URI GET /api/v1.0/gates/ 123a9s URI Parameters Hide Show gate_slug string (required) Example: 123a9s slug of gate Response 200 Hide Show Headers Content-Type : application/json Body { " id ": 423 , " slug ": "jy0iox" , " human_slug ": "" , " title ": "title" , " subtitle ": "subtitle" , " image ": "url" , " shops ": [ 320 ] , " shop_slugs ": [ "bnbgpz" ] , " start ": "2019-05-14T08:00:00Z" , " auto_queue_started ": "0001-01-01T00:00:00Z" , " type ": "open" , " state ": "running" , " message ": "" , " entries_per_minute ": 0 , " time_to_start ": - 17818393 } Gate Shops GET /api/v1.0/gates/{gate_slug}/shops Example URI GET /api/v1.0/gates/ 123a9s /shops URI Parameters Hide Show gate_slug string (required) Example: 123a9s slug of gate Response 200 Hide Show Headers Content-Type : application/json Body { [ "id" : 320 , "slug" : "bnbgpz" , "event_slug" : "xdzf0q" , "title" : "" , "subtitle" : "" , "location" : "" , "local_timezone" : "Europe/Amsterdam" , "city" : "" , "when" : "2019-12-17T19:00:00Z" , "time_till_soldout" : "2019-11-15T11:37:24.761296Z" , "state" : "no_available" , "publish_state" : "published" ] } Enter Gate POST /api/v1.0/gates/{gate_slug} Example URI POST /api/v1.0/gates/ 123a9s URI Parameters Hide Show gate_slug string (required) Example: 123a9s slug of gate Response 200 Hide Show Headers Content-Type : application/json Body { " status ": "success" , " handle ": "6aa8efbf737905bbe65a015b89c2982a" } Check Line GET /api/v1.0/line/{handle} Example URI GET /api/v1.0/line/ 123a9s URI Parameters Hide Show handle string (required) Example: 123a9s handle identifying a specific customer. Handed out by gatekeeper. Response 200 Hide Show Headers Content-Type : application/json Body { " handle ": "6aa8efbf737905bbe65a015b89c2982a" , " gate ": "jy0iox" , " number ": "" , " email ": "" , " entered ": "2019-12-06T13:33:14.435384374Z" , " position ": 1 , " last_advanced ": 0 , " minutes_till_turn ": 0.3333333333333333 , " state ": "waiting" | "turn" , " message ": "" } Retrieve Coupon POST /api/v1.0/line/{handle}/select Example URI POST /api/v1.0/line/ 123a9s /select URI Parameters Hide Show handle string (required) Example: 123a9s handle identifying a specific customer. Handed out by gatekeeper. Response 200 Hide Show Headers Content-Type : application/json Body { " status ": "success" , " coupon ": "Omc0MjM6MDo2YWE4ZWZiZjczNzkwNWJiZTY1YTAxNWI4OWMyOTgyYTo4MDkzOWRmZGZlNDg2ZGQyNDU3Zjk4OTQ5YmQzNzFkMw==" }