Introduction
Welcome to the WeTeachMe API! You can use our API to access WeTeachMe API endpoints. (Do you know that you can embed the checkout widget using JavaScript? View Docs)
The API has two families of endpoints, and they use different formats.
Products and Orders follow JSON API v1.0. Send and receive application/vnd.api+json, with bodies wrapped in data, type, and attributes.
External integration endpoints (external products, dates and tickets, bookings, transfers, refunds) use plain JSON. Send Content-Type: application/json with a flat request body as shown in each section, and expect a flat JSON response. They do not use the JSON API envelope.
Code examples are provided in Shell and Ruby. You can view them in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.
This API documentation page was created with Slate.
Authentication
To authorize, use this code:
# With shell, you can just pass the correct header with each request
curl "api_endpoint_here"
-H "API-KEY: meowmeowmeow"
# OR
curl "api_endpoint_here?api-key=meowmeowmeow"
require 'httparty'
api = HTTParty.get('some_endpoint_here', headers: { 'API-KEY' => 'meowmeowmeow' })
# OR
api = HTTParty.get('some_endpoint_here?api-key=meowmeowmeow')
Make sure to replace
meowmeowmeowwith your API key.
WeTeachMe uses API keys to allow access to the API. You can get the API key under your account settings.
API key is expected to be included in all API requests to the server in a header that looks like the following:
API-KEY: meowmeowmeow
A missing or unknown API key returns 404 with an empty body.
Products (Listings)
READ All Products (Listings)
curl
-H "Accept: application/vnd.api+json"
-H "Content-Type: application/vnd.api+json"
"https://api.weteachme.com/vendors/products?page[number]=1&page[size]=15"
-H "API-KEY: meowmeowmeow"
require 'httparty'
HTTParty.get(
"https://api.weteachme.com/vendors/products?page[number]=1&page[size]=15",
headers: {
"Accept" => 'application/vnd.api+json',
"Content-Type" => 'application/vnd.api+json'
"API-KEY" => 'meowmeowmeow'
}
);
The above command returns JSON structured like this:
{
"data":[{
"id":"1234567",
"type":"products",
"links":{
"self":"http://api.weteachme.com:3000/vendors/products/1234567"
},
"attributes":{
"name":"One On One Programme: Ages 5 to 20",
"subheading":"Personalise Your Training Plan",
"description":"</p>We provide feedback, perspective and encouragement to really develop new skills and habits to improve the young player's game.</p>",
"location-name":"41-43 Stewart St, Richmond VIC 3121, Australia",
"url":"https://weteachme.com/1111111/1234567-one-on-one-programme-ages-5-to-20",
"image":{
"original": "https://image.com/uploads/image/url/1234567/original_22869cb0-84f9-0133-03e6-02b0fd6e4061.jpg",
"large": "https://image.com/uploads/image/url/1234567/large_22869cb0-84f9-0133-03e6-02b0fd6e4061.jpg",
"medium": "https://image.com/uploads/image/url/1234567/medium_22869cb0-84f9-0133-03e6-02b0fd6e4061.jpg",
},
"status":"active",
"private":false,
"things-to-bring":["Your enthusiasm!"],
"things-to-get":["Book 2 blocks of 5 sessions and receive a 5% discount!","Expert tuition from an experienced teacher!"],
"things-to-learn":["Work at developing the latent soccer abilities of the young player and maximise player development. ","Coaching is targeted and focused on the individual's needs and goals are set for that young player. ","Individual basis to accelerate growth as a soccer player. "],
"target-audience":"5-20 years old",
"dress-code":"Sportswear",
"dates":[
{
"id":136172,
"start-datetime":"2017-02-04T10:30:00.000Z",
"end-datetime":"2017-02-05T12:30:00.000Z",
"capacity":12,
"sold":6,
"tickets-left":6,
"session-number":2,
"session-name":"Week",
"schedules":[
{ "start-datetime":"2017-02-04T10:30:00.000Z", "end-datetime":"2017-02-04T12:30:00.000Z" },
{ "start-datetime":"2017-02-05T10:30:00.000Z", "end-datetime":"2017-02-05T12:30:00.000Z" }
],
"tickets":[
{
"id":219222,
"name":"Single",
"number-of-tickets":12,
"price":"206.0",
"member-price":"200.0",
"sold":5,
"tickets-left":6,
"start-datetime":"1743-04-22T10:30:00.000Z",
"end-datetime":"2290-11-20T10:30:00.000Z",
"is-private":false,
"min":1,
"max":null
},
{
"id":219221,
"name":"VIP",
"number-of-tickets":12,
"price":"306.0",
"member-price":null,
"sold":1,
"tickets-left":6,
"start-datetime":"1743-04-22T10:30:00.000Z",
"end-datetime":"2290-11-20T10:30:00.000Z",
"is-private":true,
"min":1,
"max":null
}
]
}
]
}
}],
"links":{
"first":"http://api.weteachme.com:3000/vendors/products?page%5Bnumber%5D=1&page%5Bsize%5D=15",
"next":"http://api.weteachme.com:3000/vendors/products?page%5Bnumber%5D=2&page%5Bsize%5D=15",
"last":"http://api.weteachme.com:3000/vendors/products?page%5Bnumber%5D=16&page%5Bsize%5D=15"
}
}
This endpoint retrieves all listings.
HTTP Request
GET https://api.weteachme.com/vendors/products
Query Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
| page | Pagination (http://jsonapi.org/format/#fetching-pagination) | ||
| filter | Filtering (http://jsonapi.org/format/#fetching-filtering) |
Pagination Fields
| Parameter | Description |
|---|---|
| size | Default 15, Maximum 45 |
| number | Default 1 |
Filtering Fields
| Parameter | Type | Description |
|---|---|---|
| all | boolean | Returns all records |
| name | string | Filter by Listing Name |
| location-name | string | Filter by Listing Location |
| tag-name | string | Filter by Listing Tag Name |
| tag-id | number | Filter by Listing Tag Id |
| private | boolean | Filter by Private or Public Listing |
READ Product (Listing)
curl
-H "Accept: application/vnd.api+json"
-H "Content-Type: application/vnd.api+json"
"https://api.weteachme.com/vendors/products/1234567"
-H "API-KEY: meowmeowmeow"
require 'httparty'
HTTParty.get(
"https://api.weteachme.com/vendors/products/1234567",
headers: {
"Accept" => 'application/vnd.api+json',
"Content-Type" => 'application/vnd.api+json'
"API-KEY" => meowmeowmeow"
}
);
The above command returns JSON structured like this:
{
"id":"1234567",
"type":"products",
"links":{
"self":"http://api.weteachme.com:3000/vendors/products/1010225"
},
"attributes":{
"name":"One On One Programme: Ages 5 to 20",
"subheading":"Personalise Your Training Plan",
"description":"</p>We provide feedback, perspective and encouragement to really develop new skills and habits to improve the young player's game.</p>",
"location-name":"41-43 Stewart St, Richmond VIC 3121, Australia",
"url":"https://weteachme.com/1111111/1234567-one-on-one-programme-ages-5-to-20",
"image":{
"original": "https://image.com/uploads/image/url/1234567/original_22869cb0-84f9-0133-03e6-02b0fd6e4061.jpg",
"large": "https://image.com/uploads/image/url/1234567/large_22869cb0-84f9-0133-03e6-02b0fd6e4061.jpg",
"medium": "https://image.com/uploads/image/url/1234567/medium_22869cb0-84f9-0133-03e6-02b0fd6e4061.jpg",
},
"status":"active",
"private":false,
"things-to-bring":["Your enthusiasm!"],
"things-to-get":["Expert tuition from an experienced teacher!", "Skill Upgrades"],
"things-to-learn":["Work at developing the latent abilities of the young player and maximise player development. ","Coaching is targeted and focused on the individual's needs and goals are set for that young player. "],
"target-audience":"5-20 years old",
"dress-code":"Sportswear",
"dates":[
{
"id":136172,
"start-datetime":"2017-02-04T10:30:00.000Z",
"end-datetime":"2017-02-04T12:30:00.000Z",
"capacity":12,
"sold":6,
"tickets-left":6,
"schedules":[
{ "start-datetime":"2017-02-04T10:30:00.000Z", "end-datetime":"2017-02-04T12:30:00.000Z" },
{ "start-datetime":"2017-02-05T10:30:00.000Z", "end-datetime":"2017-02-05T12:30:00.000Z" }
],
"tickets":[
{
"id":219222,
"name":"Single",
"number-of-tickets":12,
"price":"206.0",
"member-price":"200.0",
"sold":5,
"tickets-left":6,
"start-datetime":"1743-04-22T10:30:00.000Z",
"end-datetime":"2290-11-20T10:30:00.000Z",
"is-private":false,
"min":1,
"max":null
},
{
"id":219221,
"name":"VIP",
"number-of-tickets":12,
"price":"306.0",
"member-price":null,
"sold":1,
"tickets-left":6,
"start-datetime":"1743-04-22T10:30:00.000Z",
"end-datetime":"2290-11-20T10:30:00.000Z",
"is-private":true,
"min":1,
"max":null
}
]
}
]
}
}
This endpoint retrieves all classes.
HTTP Request
GET https://api.weteachme.com/vendors/products/1234567
Orders
Create Order
curl
-X POST
-H "Accept: application/vnd.api+json"
-H "Content-Type: application/vnd.api+json"
-H "API-KEY: meowmeowmeow"
-d '{ "data": { "type": "vendors/orders", "attributes": { "ticket": { "id": 1234, "qty": 2 } } } }
"https://api.weteachme.com/vendors/orders"
require 'httparty'
HTTParty.post(
"https://api.weteachme.com/vendors/orders",
body: {
"data": {
"type": "vendors/orders",
"attributes": {
"ticket": {
"id": 1234,
"qty": 2
}
}
}
}
}.to_json,
headers: {
"Accept" => 'application/vnd.api+json',
"Content-Type" => 'application/vnd.api+json'
"API-KEY" => meowmeowmeow"
}
);
The above command returns JSON structured like this:
{
"id":"1234567",
"type":"vendors/orders",
"attributes":{
"url": "https://booking.weteachme.com/checkout?token=3a25g5AU2sxpoxhAXjESzpvBBKxr",
}
}
This endpoint generates a new order and returns the url to redirect to.
HTTP Request
POST https://api.weteachme.com/vendors/orders
JSON BODY
| key | Required | Default | Description |
|---|---|---|---|
| ticket | true | contains ticket id and qty |
External products
Use these endpoints to create or update a listing from an external catalog and to read its current WTM status. The API-KEY determines the vendor; identifiers never grant access to another vendor’s listings.
Create or update an external product
curl "https://api.weteachme.com/vendors/external_products" \
-X POST \
-H "Content-Type: application/json" \
-H "API-KEY: meowmeowmeow" \
-d '{
"external_product_id": "course-555",
"title": "Cooking Workshop",
"subheading": "Learn to cook delicious meals",
"description": "In this workshop, you will learn...",
"audience": "Adults",
"private": false,
"skill_level": "Beginner",
"location": {
"venue": "Main Kitchen",
"address": "123 Kitchen Street",
"timezone": "Australia/Melbourne",
"city": "Melbourne",
"state": "Victoria",
"country": "Australia",
"postal_code": "3000"
},
"image_url": "https://example.com/cooking-workshop.jpg",
"image_alt": "Students preparing a meal",
"learn_list": ["Kitchen skills", "Recipe techniques"],
"get_list": ["Ingredients", "Recipe notes"],
"bring_list": ["Apron"],
"dress_code": "Closed-toe shoes"
}'
require "httparty"
HTTParty.post(
"https://api.weteachme.com/vendors/external_products",
headers: { "Content-Type" => "application/json", "API-KEY" => "meowmeowmeow" },
body: {
external_product_id: "course-555",
title: "Cooking Workshop",
subheading: "Learn to cook delicious meals",
description: "In this workshop, you will learn...",
audience: "Adults",
private: false,
skill_level: "Beginner",
location: {
venue: "Main Kitchen",
address: "123 Kitchen Street",
timezone: "Australia/Melbourne",
city: "Melbourne",
state: "Victoria",
country: "Australia",
postal_code: "3000"
},
image_url: "https://example.com/cooking-workshop.jpg",
image_alt: "Students preparing a meal"
}.to_json
)
A successful create or update returns
201 Createdand the raw serialized listing, which includes many product attributes. Key fields include:
{
"id": 1234567,
"name": "Cooking Workshop",
"status": "under_review",
"external_product_id": "course-555"
}
external_product_id is the recommended stable identifier. A request with a matching vendor-owned external ID updates that listing; otherwise it creates a listing in under_review. For an existing WTM listing, omit external_product_id and supply its internal product_id. An explicit product_id that is missing or belongs to another vendor returns 422 and never creates a replacement listing.
Omitted top-level listing fields are left unchanged. If you send any of audience, learn_list, get_list, bring_list, or dress_code, include the complete desired set of those metadata fields because that metadata object is replaced. Send status: "active" to reactivate an archived listing. The optional dates array uses the same format as External dates and tickets.
Product fields
| Field | Required | Description |
|---|---|---|
| external_product_id | Recommended | Stable ID in the external system; used to create or update the vendor’s listing |
| product_id | Alternative | Internal WTM listing ID; used only when external_product_id is omitted |
| title | New listings | Listing title |
| subheading | No | Short listing summary |
| description | No | Full listing description; HTML is accepted |
| status | No | active reactivates a matching archived listing |
| audience | No | Intended audience |
| private | No | Whether the listing is private |
| skill_level | No | Skill level label |
| seo_meta_title | No | SEO title |
| seo_meta_description | No | SEO description |
| image_url | No | Publicly reachable listing image URL |
| image_alt | No | Accessible image description |
| learn_list | No | Array of learning outcomes |
| get_list | No | Array of inclusions |
| bring_list | No | Array of things attendees should bring |
| dress_code | No | Clothing or safety guidance |
| location | No | Venue object; matching venue and address reuse an existing vendor location |
| dates | No | Dates and tickets to synchronize with the listing |
Location fields
| Field | Required | Description |
|---|---|---|
| venue | Yes | Venue name |
| address | Yes | Street address |
| lat | No | Latitude |
| lng | No | Longitude |
| timezone | No | IANA timezone, for example Australia/Melbourne |
| city | No | City |
| state | No | State or region |
| country | No | Country |
| postal_code | No | Postal code |
| parking | No | Parking instructions |
Read external product status
curl "https://api.weteachme.com/vendors/external_products/course-555" \
-H "Content-Type: application/json" \
-H "API-KEY: meowmeowmeow"
require "httparty"
HTTParty.get(
"https://api.weteachme.com/vendors/external_products/course-555",
headers: { "Content-Type" => "application/json", "API-KEY" => "meowmeowmeow" }
)
The path identifier is the external product ID, not the WTM product ID.
{
"status": "active"
}
The endpoint returns 200 for a matching vendor-owned listing and 404 when the external ID is unknown or belongs to another vendor.
Response statuses
| Status | Meaning |
|---|---|
| 201 | Listing created or updated |
| 200 | External product status returned |
| 404 | External product is not found for the authenticated vendor; empty response body |
| 413 | Request exceeds 1 MiB (request_too_large) |
| 422 | Product validation fails, or payment credentials are rejected (sensitive_payment_data) |
| 503 | Request auditing is unavailable (request_audit_unavailable); no listing mutation is attempted |
External dates and tickets
Use this endpoint to add or update scheduled dates and ticket types on an existing vendor listing. Identify the listing by external_product_id or by its internal WTM product_id. Both lookups are restricted to the vendor authenticated by API-KEY.
Synchronize dates and tickets
curl "https://api.weteachme.com/vendors/external_dates_tickets" \
-X POST \
-H "Content-Type: application/json" \
-H "API-KEY: meowmeowmeow" \
-d '{
"external_product_id": "course-555",
"dates": [{
"external_date_id": "course-555-2026-10-24-10am-1pm",
"start_date": "2026-10-24",
"start_time": "10:00 am",
"end_date": "2026-10-24",
"end_time": "1:00 pm",
"capacity": 12,
"tipping_point": 1,
"sold": 0,
"status": "active",
"is_private": true,
"tickets": [{
"name": "General admission",
"description": "Workshop admission",
"ticket_type": "full_price",
"open_difference": -720,
"close_difference": -24,
"qty": 12,
"price": 95.00
}]
}]
}'
require "httparty"
HTTParty.post(
"https://api.weteachme.com/vendors/external_dates_tickets",
headers: { "Content-Type" => "application/json", "API-KEY" => "meowmeowmeow" },
body: {
external_product_id: "course-555",
dates: [{
external_date_id: "course-555-2026-10-24-10am-1pm",
start_date: "2026-10-24",
start_time: "10:00 am",
end_date: "2026-10-24",
end_time: "1:00 pm",
capacity: 12,
tipping_point: 1,
sold: 0,
status: "active",
is_private: true,
tickets: [{
name: "General admission",
description: "Workshop admission",
ticket_type: "full_price",
open_difference: -720,
close_difference: -24,
qty: 12,
price: 95.00
}]
}]
}.to_json
)
A successful request returns
201 Created.
{
"message": "1 dates successfully created"
}
external_date_id is the date idempotency key within the listing. Replaying it updates the existing date instead of creating another one. Tickets on that date are updated by exact ticket name; keep ticket names unchanged between synchronization runs.
Upload reviewable dates with is_private: true. Publish an approved date by replaying the same external_date_id and ticket names with is_private: false.
Top-level fields
| Field | Required | Description |
|---|---|---|
| external_product_id | One identifier | Stable external listing ID |
| product_id | One identifier | Internal WTM listing ID; used only when external_product_id is omitted |
| dates | Yes | Non-empty array of date objects |
Date fields
| Field | Required | Description |
|---|---|---|
| external_date_id | Recommended | Stable external date ID used for updates and safe replays |
id-code |
No | External source code stored in WTM as external_code; it is not an update key |
| start_date | Yes | Start date in YYYY-MM-DD format |
| start_time | Yes | Start time, for example 10:00 am |
| end_date | Yes | End date in YYYY-MM-DD format |
| end_time | Yes | End time |
| capacity | Yes | Total date capacity |
| tipping_point | Yes | Minimum attendance threshold |
| sold | Yes | Existing sold quantity in the source system |
| status | No | Defaults to active |
| cancelled | No | Defaults to false |
| is_private | No | Hide or publish the date |
| tickets | Yes | Non-empty array of ticket objects |
Ticket fields
| Field | Required | Description |
|---|---|---|
| name | Yes | Ticket name and update key within the date |
| description | No | Ticket description |
| ticket_type | Yes | Ticket type, commonly full_price |
| qty | Yes | Ticket quantity; 0 uses the date capacity |
| price | Yes | Ticket price |
| open_difference | No | Booking-open time in hours relative to class start; omitted uses the API default |
| close_difference | No | Booking-close time in hours relative to class start; -24 closes 24 hours before |
| weight | No | Display order; omitted uses array order |
| ticket_merchandises | No | Compulsory merchandise rows containing merchandise_id and qty |
Response statuses
| Status | Meaning |
|---|---|
| 201 | Dates and tickets synchronized |
| 413 | Request exceeds 1 MiB (request_too_large) |
| 422 | Listing/date/ticket validation fails, or payment credentials are rejected (sensitive_payment_data) |
| 503 | Request auditing is unavailable (request_audit_unavailable); no synchronization is attempted |
The controller currently returns 422 for unexpected internal failures as well as validation failures. In those cases errors may be a bare string; message remains human-readable. A multi-date request is transactional: a validation failure rolls back that synchronization call.
External bookings
Use this endpoint to record a booking that an external service has already confirmed and paid. It creates the WTM order and attendee records but does not collect money or call a payment gateway.
The API-KEY determines the vendor. The request does not accept a vendor or company ID. Obtain the vendor’s internal ticketing_id values from the ticket data returned by the vendor Products API before recording a booking.
Record an external booking
curl "https://api.weteachme.com/vendors/external_bookings" \
-X POST \
-H "Content-Type: application/json" \
-H "API-KEY: meowmeowmeow" \
-d '{
"source": "partner_portal",
"external_booking_id": "booking-123",
"booked_at": "2026-08-25T10:15:00+10:00",
"notify_customer_enrolment": true,
"notify_customer_payment": false,
"notify_vendor": false,
"purchaser": {
"firstname": "Billie",
"lastname": "Buyer",
"email": "billie@example.com",
"mobile": "0400000000",
"requirements": "Wheelchair access"
},
"line_items": [{
"ticketing_id": 123,
"unit_price": "55.00",
"quantity": 2
}],
"payment": {
"amount": "110.00",
"currency": "AUD",
"reference": "payment-456"
}
}'
require "httparty"
HTTParty.post(
"https://api.weteachme.com/vendors/external_bookings",
headers: { "Content-Type" => "application/json", "API-KEY" => "meowmeowmeow" },
body: {
source: "partner_portal",
external_booking_id: "booking-123",
booked_at: "2026-08-25T10:15:00+10:00",
notify_customer_enrolment: true,
notify_customer_payment: false,
notify_vendor: false,
purchaser: {
firstname: "Billie",
lastname: "Buyer",
email: "billie@example.com",
requirements: "Wheelchair access"
},
line_items: [{
ticketing_id: 123,
unit_price: "55.00",
quantity: 2
}],
payment: { amount: "110.00", currency: "AUD", reference: "payment-456" }
}.to_json
)
A new booking returns
201 Created.
{
"order_id": 987,
"external_booking_id": "booking-123",
"source": "partner_portal",
"status": "completed",
"replayed": false
}
Each line item books seats either by count or by naming each attendee:
line_items[].quantityis a positive JSON integer. WTM creates one attendee per seat and copies the purchaser details to every attendee.line_items[].attendeesis a non-empty array of attendee objects. Each attendee requiresfirstname,lastname, andemail, and accepts optionalmobileandrequirements. Other attendee fields are rejected.
At least one of the two is required. When both are present, quantity must equal the number of attendees.
line_items[].unit_price is the actual price paid per seat. Prices and payment.amount must be finite decimal values. The sum of unit_price × seats for all lines must equal payment.amount, and payment.currency must match the vendor’s currency. Ticketings must be active, owned by the authenticated vendor, have enough capacity, and use the full-amount payment option; instalment and recurring ticketings are not supported.
Purchasers accept firstname, lastname, email, mobile, and optional requirements. Other purchaser fields are rejected. Purchaser and attendee emails must be valid email addresses, and mobile numbers must be strings of at most 32 characters. requirements, source, external_booking_id, payment.reference, and every purchaser or attendee firstname, lastname, and email must be strings of at most 255 characters. booked_at must describe a time that has already occurred; future timestamps are rejected.
Request bodies are limited to 1 MiB. Larger requests return 413 with error_code: "request_too_large". Do not send authentication secrets, card details, or bank credentials in the booking body; payment credentials are rejected before booking state is created.
Messages
WTM sends no messages for an external booking unless the request asks for them. All three flags are optional JSON booleans that default to false:
notify_customer_enrolmentsends the customer one enrolment confirmation per customer and ticket (seats seeded from the purchaser share one email). This is the email that carries the class details and the attendee details link.notify_customer_paymentsends the customer the payment confirmation.notify_vendorsends the vendor a booking notification for every attendee plus the payment notification.
Regardless of these flags, every attendee still gets WTM’s normal post-enrolment setup: class-pass coupons, LMS access for on-demand courses, review invites, and waiting-list removal.
The retired capture_attendee_details, email_opt_out, and notify_customer keys are rejected with 422.
Attendee details
External booking creation never rejects a request because of the vendor’s configured booking questions. Attendee, custom-field, and extra-field validation is bypassed while the booking is created.
After creation, WTM checks each attendee against the vendor’s booking rules: name and email, mobile where the vendor requires it, and every enabled extra field and required custom field for the ticket. Attendees that satisfy every rule are marked complete. Attendees that are missing details stay open, and the attendee confirmation email includes an “Update Attendee Information” link until the purchaser completes them through the WTM attendee page, where the normal configured validations apply.
The order also records registration_context.external_booking with the source, external_booking_id, booked_at, both message flags, and whether attendees were supplied explicitly.
Retry behavior
The combination of the authenticated vendor, source, and external_booking_id is the idempotency key. Retry a timed-out request with the same complete payload. Normalization ignores JSON object key ordering; values and array ordering must remain identical.
- An identical normalized payload returns the original order with
200 OKandreplayed: true. - The same key with changed quantity, attendees, message flags, requirements, ticket, payment, or other business data returns
409 Conflictwitherror_code: "idempotency_conflict". - Invalid content returns
400or422; a ticket outside the authenticated vendor returns404.
Do not reuse an external booking ID for a different booking.
Response statuses
| Status | Meaning |
|---|---|
| 201 | New booking recorded and completed |
| 200 | Identical idempotent replay; no duplicate order, attendee, payment, or inventory change |
| 400 | Malformed JSON (invalid_json) |
| 404 | A ticket does not exist for the authenticated vendor |
| 409 | The idempotency key already exists with different business content |
| 413 | Request body exceeds 1 MiB |
| 422 | Structurally invalid payload or business validation failed, including ticket availability, amount, currency, or prohibited payment credentials |
| 503 | The request audit could not be created, so no booking mutation was attempted |
External transfers
Use this endpoint to move an attendee to another ticket owned by the same vendor. The transfer is recorded as attendee-initiated and may collect a transfer fee from a reusable card already stored on the order.
Transfer an attendee
curl "https://api.weteachme.com/vendors/external_transfers" \
-X POST \
-H "Content-Type: application/json" \
-H "API-KEY: meowmeowmeow" \
-d '{
"order_id": 987,
"attendee_id": 654,
"to_product_id": 321,
"to_teaching_id": 432,
"to_ticketing_id": 543,
"subtotal": 10.00,
"transfer_charge": 10.00
}'
require "httparty"
HTTParty.post(
"https://api.weteachme.com/vendors/external_transfers",
headers: { "Content-Type" => "application/json", "API-KEY" => "meowmeowmeow" },
body: {
order_id: 987,
attendee_id: 654,
to_product_id: 321,
to_teaching_id: 432,
to_ticketing_id: 543,
subtotal: 10.00,
transfer_charge: 10.00
}.to_json
)
A successful transfer returns
201 Created.
{
"transfer_id": 765,
"attendee_id": 654,
"status": "transferred",
"payment_status": "paid"
}
Request fields
| Field | Required | Description |
|---|---|---|
| order_id | Yes | Existing order containing the attendee |
| attendee_id | Yes | Active attendee to transfer |
| to_product_id | Yes | Destination WTM listing ID |
| to_teaching_id | Yes | Destination date/session ID |
| to_ticketing_id | Yes | Destination ticket ID |
| subtotal | No | Transfer amount; defaults to 0; a positive value must be at least 2.00 in every currency |
| transfer_charge | No | Transfer fee; defaults to 0 and must equal subtotal |
| charge_now | No | Boolean override; omitted charges now for a free transfer or when a reusable card exists |
payment_status is paid when the fee is settled during the request and pending_payment when the attendee is moved but the fee remains due. If a stored-card charge returns 422 with error_code: "charge_declined", the caller may retry with charge_now: false to complete the held, pay-later transfer.
| Status | Meaning |
|---|---|
| 201 | Attendee transferred; inspect payment_status |
| 404 | The order, attendee, destination listing, date, or ticket is missing or belongs to another vendor; empty response body |
| 413 | Request exceeds 1 MiB (request_too_large) |
| 422 | Invalid amount, same-ticket transfer, past cutoff, unavailable destination, payment failure, or rejected payment credentials (sensitive_payment_data) |
| 503 | Request auditing is unavailable (request_audit_unavailable); no transfer is attempted |
Successful requests move inventory and send the transfer confirmation asynchronously. This endpoint has no idempotency key; do not blindly replay a request that may already have succeeded.
External refunds
Use this endpoint to execute a refund decision that an external policy service has already approved. It refunds the named payment or issues a credit note, then cancels the named attendee spots and returns inventory.
Execute an approved refund
curl "https://api.weteachme.com/vendors/external_refunds" \
-X POST \
-H "Content-Type: application/json" \
-H "API-KEY: meowmeowmeow" \
-d '{
"payment_id": 9497,
"amount": 55.00,
"attendee_ids": [8844],
"refund_reason": "customer_requested",
"credit_note": false,
"note": "Approved by the partner cancellation policy"
}'
require "httparty"
HTTParty.post(
"https://api.weteachme.com/vendors/external_refunds",
headers: { "Content-Type" => "application/json", "API-KEY" => "meowmeowmeow" },
body: {
payment_id: 9497,
amount: 55.00,
attendee_ids: [8844],
refund_reason: "customer_requested",
credit_note: false,
note: "Approved by the partner cancellation policy"
}.to_json
)
A successful refund returns
201 Created.
{
"refund_id": 2468,
"status": "completed"
}
Request fields
| Field | Required | Description |
|---|---|---|
| payment_id | Yes | Payment to refund; must belong to the authenticated vendor |
| amount | Yes | Approved refund or credit-note amount; cannot exceed the remaining refundable amount |
| attendee_ids | No | Attendees whose spots should be cancelled; each must belong to the payment’s order |
| refund_reason | No | Approved reason, for example customer_requested or class_cancelled |
| credit_note | No | true issues vendor credit instead of refunding through the payment gateway |
| expiry_date | No | Optional credit-note expiry date |
| note | No | Internal note recorded with the refund |
All named attendees are validated before money moves. An already-cancelled attendee returns 422; a missing or foreign payment/attendee returns 404. A successful request cancels the attendees and decreases the associated ticket sold counts.
| Status | Meaning |
|---|---|
| 201 | Refund or credit note completed |
| 404 | Payment or attendee not found within the authenticated vendor’s booking; empty response body |
| 413 | Request exceeds 1 MiB (request_too_large) |
| 422 | Amount exceeds the refundable balance, an attendee is already cancelled, another refund rule fails, or payment credentials are rejected (sensitive_payment_data) |
| 503 | Request auditing is unavailable (request_audit_unavailable); no refund is attempted |
This endpoint has no idempotency key. After an ambiguous timeout, verify the payment and attendee state in WTM before deciding whether to retry.
Errors
Products and Orders
These endpoints follow JSON API. Errors come back as a JSON API errors array, each entry carrying status, title, and detail.
External endpoints
The external endpoints return plain JSON. When the endpoint defines an error code, the body is:
{
"error_code": "invalid_payload",
"errors": ["quantity must be a positive integer"]
}
Where no code is defined, the body is just errors:
{
"errors": ["Listing not found for product_id course-555"]
}
errors is always an array of human-readable strings.
| HTTP status | error_code |
Endpoints | When |
|---|---|---|---|
| 400 | invalid_json |
bookings | Request body is not valid JSON. |
| 404 | none, empty body | all | API key missing or unknown, or the record does not belong to the authenticated vendor. |
| 404 | not_found |
bookings | A ticketing_id in the request was not found. |
| 409 | idempotency_conflict |
bookings | Same source + external_booking_id as an earlier booking, but different content. |
| 413 | request_too_large |
all external | Request body over 1 MiB. |
| 422 | invalid_payload |
bookings | A field is missing, the wrong type, over its limit, or a retired key was sent. |
| 422 | booking_invalid |
bookings | The booking failed a business rule, for example a sold-out ticket. |
| 422 | charge_declined |
transfers | The stored card was declined for the transfer fee. Retry with charge_now: false. |
| 422 | sensitive_payment_data |
all external | Card or bank credentials were found in the body. They are never accepted. |
| 422 | none | products, dates and tickets, transfers, refunds | Validation failed; see errors. Dates and tickets also returns message. |
| 503 | request_audit_unavailable |
all external | Request auditing is down. Retry later; nothing was created. |