# Heights Platform API (v1) This document describes the currently available Heights Platform JSON API endpoints that can be used by customers and integration partners. ## Base URL (multi-tenant) All customer API requests are made against the **customer’s account subdomain**: - `https://{account_subdomain}.heightsplatform.com/api/v1/...` ## Authentication Heights uses token authentication via an API key associated with an **admin user**. Send the API key in the `Authorization` header: ```text Authorization: Token token=YOUR_API_KEY ``` Bearer tokens are also accepted: ```text Authorization: Bearer YOUR_API_KEY ``` Where to get your API key: - In the app UI: **Account Settings** (shows your current user’s `api_key`). ### Admin-only API access Customer API endpoints require an API key for an admin user in the Heights account. A valid key belonging to a non-admin user returns HTTP **403 Forbidden**. ### Unauthorized responses If the token is missing or invalid, the API responds with HTTP **401 Unauthorized**. ## Content type - Responses are JSON. - Requests should use `Content-Type: application/json` when sending a JSON body. ## Response headers Authenticated v1 API responses include: - `X-Request-Id` – request identifier useful when contacting support. - `X-API-Version: v1` ## Rate limiting Public API requests are rate limited per account: - Standard accounts: **60 requests per minute** - Academy plan accounts: **300 requests per minute** The account-level limit is shared across API keys and endpoints for the same account. Requests over the limit return HTTP **429 Too Many Requests**. ## Pagination Students index is paginated by default. - `GET /students?page=N` - Page size is fixed at **50**. Orders support optional pagination. - `GET /orders?page=N&per_page=50` - `per_page` is capped at **100**. Paginated responses include a `meta` block. ```json { "meta": { "total_pages": 12, "total_students": 587 } } ``` ## Errors The API uses standard HTTP status codes and returns JSON errors. - **401**: invalid/missing API token - **403**: token is valid but not allowed, such as a non-admin API key - **404**: record not found / endpoint not available - **422**: validation error or invalid parameter - **429**: rate limit exceeded Example error response: ```json { "error": "Record not found.", "errors": [ { "code": "not_found", "message": "Record not found." } ], "request_id": "..." } ``` --- # Endpoints ## Account ### Get account and API capabilities `GET /api/v1/account` Returns the authenticated admin user, account metadata, available webhook events, and a capabilities summary. Example response: ```json { "account": { "id": 1, "subdomain": "academy", "app_name": "Academy", "custom_domain": null, "currency": "USD", "locale": "en" }, "user": { "id": 10, "name": "Admin Name", "email": "admin@example.com", "admin": true }, "capabilities": { "admin_api": true, "read_courses": true, "read_bundles": true, "read_digital_products": true, "read_students": true, "manage_student_access": true, "read_orders": true, "read_order_webhook_payloads": true, "webhooks": true }, "webhook_events": [ "new_student", "new_order", "course_completed", "student_completed", "new_answer", "new_project_post" ] } ``` --- ## Courses ### List courses `GET /api/v1/courses` Returns **published** courses. Example response: ```json { "courses": [ { "id": 123, "title": "Example Course", "sold_separately": true, "price": "99.0" } ] } ``` ### Get course `GET /api/v1/courses/:id` Example response: ```json { "id": 123, "title": "Example Course", "description": "...", "slug": "example-course", "sold_separately": true, "price": "99.0", "is_published": true, "is_challenge": false, "cover_image_url": "https://...", "cover_image_thumbnail_url": "https://...", "unsplash_image_url": "https://...", "lesson_count": 12 } ``` ### List roles `GET /api/v1/courses/roles` Example response: ```json { "roles": [ { "id": 1, "name": "Gold" } ] } ``` ### Course completion percentage (for a specific student) `POST /api/v1/courses/:id/completion-percentage` Parameters: - `user_email` (string, required) Example request: ```bash curl -X POST \ -H 'Authorization: Token token=YOUR_API_KEY' \ -d 'user_email=student@example.com' \ https://{subdomain}.heightsplatform.com/api/v1/courses/123/completion-percentage ``` --- ## Bundles (Offers) ### List bundles `GET /api/v1/bundles` Example response: ```json { "bundles": [ { "id": 10, "title": "Starter Offer" } ] } ``` ### Get bundle `GET /api/v1/bundles/:id` Example response: ```json { "id": 10, "title": "Starter Offer", "description": "...", "slug": "starter-offer", "is_published": true, "cover_image_thumbnail_url": "https://...", "product_count": 3 } ``` --- ## Digital products ### List digital products `GET /api/v1/digital_products` ### Get digital product `GET /api/v1/digital_products/:id` Example response: ```json { "id": 7, "title": "Workbook PDF", "description": "...", "product_type": "download", "slug": "workbook-pdf", "price": "29.0", "is_published": true, "cover_image_url": "https://..." } ``` --- ## Orders ### List orders `GET /api/v1/orders` Returns paid orders by default (`status=paid`). Each order uses the same field shape as the `new_order` webhook payload. Optional query parameters: - `status` – one of `pending`, `failed`, `paid`, `paypal_executed`, or `all` (default: `paid`) - `user_email` – exact student email match - `created_after` – ISO-8601 date/time lower bound - `created_before` – ISO-8601 date/time upper bound - `page` – enable pagination - `per_page` – page size when `page` is present, capped at 100 Example request: ```bash curl -H 'Authorization: Token token=YOUR_API_KEY' \ 'https://{subdomain}.heightsplatform.com/api/v1/orders?user_email=student@example.com&page=1&per_page=50' ``` Example response: ```json { "orders": [ { "id": "ORD_ABC123", "amount": "99.0", "currency": "USD", "description": "Course Purchase", "user_email": "student@example.com", "user_name": "Student Name", "user_first_name": "Student", "user_last_name": "Name", "status": "paid", "affiliate_id": null, "courses": [ { "id": 123, "title": "Example Course" } ], "digital_products": [], "bundles": [], "billing_city": "Austin", "billing_state": "TX", "billing_country": "US", "billing_street": "123 Main St", "billing_zip": "78701", "billing_company": null, "billing_phone": null, "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-01T12:00:00Z" } ], "meta": { "total_pages": 1, "total_orders": 1, "current_page": 1, "per_page": 50 } } ``` ### Get order `GET /api/v1/orders/:id` `:id` is the order’s public `unique_order` value returned as `id` by the order list and webhook payload. The response is a single order object using the same field shape as the `new_order` webhook payload. --- ## Students ### List students (paginated) `GET /api/v1/students?page=N` ### Student details (by email) `GET /api/v1/students/details?email=student@example.com` Returns: - student attributes - lesson views count - lesson completions count - paid orders - enrolled courses ### Students with 100% completion `GET /api/v1/students/completions` Returns students where `total_completion >= 100`. ### Assignment answers `GET /api/v1/students/answers` Returns all assignment answers. ### Project posts `GET /api/v1/students/project-posts` Returns all project posts. --- ## Student access management All of the following endpoints are **POST** requests under `resource :student`. ### Enroll (create or update student, grant access) `POST /api/v1/student/enroll` Parameters: - `email` (string, required) - `name` (string, required when creating a new student) - `course_id` (integer, optional) – grants access to a course by creating a $0 paid order - `bundle_id` (integer, optional) – grants access to a bundle by creating a $0 paid order ### Unenroll from paid membership `POST /api/v1/student/unenroll` Parameters: - `email` (string, required) - `name` (string, required) Effect: sets `paying_student=false`. ### Revoke course access `POST /api/v1/student/revoke-course` Parameters: - `email` (string, required) - `name` (string, required) - `course_id` (integer, required) ### Revoke bundle access `POST /api/v1/student/revoke-bundle` Parameters: - `email` (string, required) - `bundle_id` (integer, required) ### Grant role `POST /api/v1/student/grant-role` Parameters: - `email` (string, required) - `role_id` (integer, required) ### Revoke role `POST /api/v1/student/revoke-role` Parameters: - `email` (string, required) - `role_id` (integer, required) ### Reset course progress `POST /api/v1/student/reset-course-progress` Parameters: - `email` (string, required) - `course_id` (integer, required) ### Reset bundle progress `POST /api/v1/student/reset-bundle-progress` Parameters: - `email` (string, required) - `bundle_id` (integer, required) --- ## Webhooks Heights has an existing webhook system powered by the app’s webhook subscriptions. Do **not** build duplicate webhook routes for customer integrations; use the existing webhook subscription UI. ### Subscribe to webhooks 1. Sign in to the Heights account as an admin. 2. Go to **Account Settings → Integrations → Webhooks**. 3. Enter a target URL. 4. Choose an event. 5. Save the subscription. Heights will send event payloads to the configured target URL when matching events occur. ### Available events - `new_student` – triggered when a student enrolls in the program. - `new_order` – triggered when an order is generated, including paid purchases and granted access orders. - `course_completed` – triggered when a student completes a course. - `student_completed` – triggered when a student reaches 100% program completion. - `new_answer` – triggered when a student submits an assignment answer. - `new_project_post` – triggered when a student posts in a project. ### `new_order` payload The `new_order` webhook payload matches `GET /api/v1/orders/:id`. ```json { "id": "ORD_ABC123", "amount": "99.0", "currency": "USD", "description": "Course Purchase", "user_email": "student@example.com", "user_name": "Student Name", "user_first_name": "Student", "user_last_name": "Name", "status": "paid", "affiliate_id": null, "courses": [ { "id": 123, "title": "Example Course" } ], "digital_products": [], "bundles": [], "billing_city": "Austin", "billing_state": "TX", "billing_country": "US", "billing_street": "123 Main St", "billing_zip": "78701", "billing_company": null, "billing_phone": null, "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-01T12:00:00Z" } ``` ### Other webhook payloads `new_student`: ```json { "id": 55, "name": "Student Name", "email": "student@example.com" } ``` `course_completed`: ```json { "id": 55, "name": "Student Name", "email": "student@example.com", "course": "Example Course", "course_id": 123 } ``` `student_completed`: ```json { "id": 55, "name": "Student Name", "email": "student@example.com" } ``` `new_answer`: ```json { "id": 55, "name": "Student Name", "email": "student@example.com", "answer": "My answer text", "question": "Assignment prompt", "lesson": "Lesson Title" } ``` `new_project_post`: ```json { "id": 55, "name": "Student Name", "email": "student@example.com", "post": "Project post title" } ```