Repository: r-spacex/SpaceX-API Branch: master Commit: 9f56af14a75a Files: 161 Total size: 304.4 KB Directory structure: gitextract_mkb_w92a/ ├── .dockerignore ├── .eslintrc.json ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── codeql-analysis.yml │ └── deploy.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app.js ├── docs/ │ ├── README.md │ ├── apps.md │ ├── capsules/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── clients.md │ ├── company/ │ │ └── v4/ │ │ ├── all.md │ │ └── schema.md │ ├── cores/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── crew/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── dragons/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── history/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── landpads/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── launches/ │ │ ├── v4/ │ │ │ ├── all.md │ │ │ ├── latest.md │ │ │ ├── next.md │ │ │ ├── one.md │ │ │ ├── past.md │ │ │ ├── query.md │ │ │ ├── schema.md │ │ │ └── upcoming.md │ │ └── v5/ │ │ ├── README.md │ │ ├── all.md │ │ ├── latest.md │ │ ├── next.md │ │ ├── one.md │ │ ├── past.md │ │ ├── query.md │ │ ├── schema.md │ │ └── upcoming.md │ ├── launchpads/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── payloads/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── queries.md │ ├── roadster/ │ │ └── v4/ │ │ ├── get.md │ │ ├── query.md │ │ └── schema.md │ ├── rockets/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ ├── ships/ │ │ └── v4/ │ │ ├── all.md │ │ ├── one.md │ │ ├── query.md │ │ └── schema.md │ └── starlink/ │ └── v4/ │ ├── all.md │ ├── one.md │ ├── query.md │ └── schema.md ├── jobs/ │ ├── capsules.js │ ├── cores.js │ ├── landpads.js │ ├── launch-library.js │ ├── launches.js │ ├── launchpads.js │ ├── payloads.js │ ├── roadster.js │ ├── starlink.js │ ├── upcoming.js │ ├── webcast.js │ └── worker.js ├── lib/ │ ├── constants.js │ ├── healthchecks/ │ │ ├── fail.js │ │ ├── index.js │ │ ├── start.js │ │ └── success.js │ └── utils/ │ └── healthcheck.js ├── middleware/ │ ├── auth.js │ ├── authz.js │ ├── cache.js │ ├── errors.js │ ├── index.js │ ├── logger.js │ └── response-time.js ├── models/ │ ├── capsules.js │ ├── company.js │ ├── cores.js │ ├── crew.js │ ├── dragons.js │ ├── history.js │ ├── index.js │ ├── landpads.js │ ├── launches.js │ ├── launchpads.js │ ├── payloads.js │ ├── roadster.js │ ├── rockets.js │ ├── ships.js │ ├── starlink.js │ └── users.js ├── package.json ├── routes/ │ ├── admin/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── capsules/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── company/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── cores/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── crew/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── dragons/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── history/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── index.js │ ├── landpads/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── launches/ │ │ ├── index.js │ │ ├── v4/ │ │ │ ├── _transform-query.js │ │ │ ├── _transform-response.js │ │ │ └── index.js │ │ └── v5/ │ │ └── index.js │ ├── launchpads/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── payloads/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── roadster/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── rockets/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── ships/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ ├── starlink/ │ │ ├── index.js │ │ └── v4/ │ │ └── index.js │ └── users/ │ ├── index.js │ └── v4/ │ └── index.js ├── server.js ├── start.sh └── tests/ └── index.test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .git node_modules npm-debug.log *.md test docs Dockerfile coverage LICENSE .eslintrc .gitignore .nvmrc .env ================================================ FILE: .eslintrc.json ================================================ { "env": { "node": true, "es2022": true, "jest": true }, "extends": ["airbnb-base"], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "plugins": ["@typescript-eslint"], "overrides": [ { "files": ["tests/**"], "plugins": ["jest"], "extends": ["plugin:jest/recommended"] } ], "rules": { "no-console": 0, "import/extensions": ["error", "always"], "import/no-unresolved": ["error", { "ignore": ["^got$"] }] } } ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: npm directory: "/" schedule: interval: weekly day: saturday open-pull-requests-limit: 10 ignore: - dependency-name: eslint versions: - 7.21.0 - 7.24.0 - dependency-name: eslint-plugin-no-secrets versions: - 0.8.9 ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: [master, ] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: '0 4 * * 2' jobs: analyse: name: Analyse runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 ================================================ FILE: .github/workflows/deploy.yml ================================================ name: Deploy on: push: # Publish `master` as Docker `latest` image. branches: - master # Publish `v1.2.3` tags as releases. tags: - v* pull_request: env: IMAGE_NAME: spacex-api jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: "18.x" - run: npm install - run: npm run check-dependencies - run: npm test # Push image to GitHub Packages. # See also https://docs.docker.com/docker-hub/builds/ push: # Ensure test job passes before pushing image. needs: test runs-on: ubuntu-latest if: github.event_name == 'push' steps: - uses: actions/checkout@v2 - name: Build image run: docker build . --file Dockerfile --tag $IMAGE_NAME - name: Log into registry run: echo "${{ secrets.GITLAB_PASSWORD }}" | docker login registry.gitlab.com -u ${{ secrets.GITLAB_USERNAME }} --password-stdin - name: Push image run: | IMAGE_ID=registry.gitlab.com/jakewmeyer/spacex-api # Change all uppercase to lowercase IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') # Strip git ref prefix from version VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') # Strip "v" prefix from tag name [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') # Use Docker `latest` tag convention [ "$VERSION" == "master" ] && VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # General *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # JetBrains IDE project files /.idea TODO.md test.json spacex-api .envrc ================================================ FILE: Dockerfile ================================================ FROM node:18-alpine LABEL maintainer="jakewmeyer@gmail.com" LABEL autoheal="true" HEALTHCHECK --interval=10s --timeout=3s \ CMD ./lib/utils/healthcheck.js RUN apk add --no-cache --upgrade bash ENV NODE_ENV=production ENV HEALTH_URL=http://localhost:6673/v4/admin/health EXPOSE 6673 # Run as an unprivileged user. RUN addgroup -S spacex && adduser -S -G spacex spacex RUN mkdir /app && chown spacex /app USER spacex WORKDIR /app ENTRYPOINT ["/app/start.sh"] COPY --chown=spacex:spacex package.json package-lock.json /app/ RUN npm install --production COPY --chown=spacex:spacex . . ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017-2022 Jake Meyer Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================

SpaceX REST API

Open Source REST API for launch, rocket, core, capsule, starlink, launchpad, and landing pad data.

We are not affiliated, associated, authorized, endorsed by, or in any way officially connected with Space Exploration Technologies Corp (SpaceX), or any of its subsidiaries or its affiliates. The names SpaceX as well as related names, marks, emblems and images are registered trademarks of their respective owners.

Docs - API Clients - Apps - Status - Database Exports

## Usage ``` GET https://api.spacexdata.com/v5/launches/latest ``` ```json { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/eb/0f/Vev7xkUX_o.png", "large": "https://images2.imgbox.com/ab/79/Wyc9K7fv_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/fjf6rr/dm2_launch_campaign_thread/", "launch": "https://www.reddit.com/r/spacex/comments/glwz6n/rspacex_cctcap_demonstration_mission_2_general", "media": "https://www.reddit.com/r/spacex/comments/gp1gf5/rspacex_dm2_media_thread_photographer_contest/", "recovery": "https://www.reddit.com/r/spacex/comments/gu5gkd/cctcap_demonstration_mission_2_stage_1_recovery/" }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49927519643_b43c6d4c44_o.jpg", "https://live.staticflickr.com/65535/49927519588_8a39a3994f_o.jpg", "https://live.staticflickr.com/65535/49928343022_6fb33cbd9c_o.jpg", "https://live.staticflickr.com/65535/49934168858_cacb00d790_o.jpg", "https://live.staticflickr.com/65535/49934682271_fd6a31becc_o.jpg", "https://live.staticflickr.com/65535/49956109906_f88d815772_o.jpg", "https://live.staticflickr.com/65535/49956109706_cffa847208_o.jpg", "https://live.staticflickr.com/65535/49956109671_859b323ede_o.jpg", "https://live.staticflickr.com/65535/49955609618_4cca01d581_o.jpg", "https://live.staticflickr.com/65535/49956396622_975c116b71_o.jpg", "https://live.staticflickr.com/65535/49955609378_9b77e5c771_o.jpg", "https://live.staticflickr.com/65535/49956396262_ef41c1d9b0_o.jpg" ] }, "presskit": "https://www.nasa.gov/sites/default/files/atoms/files/commercialcrew_press_kit.pdf", "webcast": "https://youtu.be/xY96v0OIcK4", "youtube_id": "xY96v0OIcK4", "article": "https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/", "wikipedia": "https://en.wikipedia.org/wiki/Crew_Dragon_Demo-2" }, "static_fire_date_utc": "2020-05-22T17:39:00.000Z", "static_fire_date_unix": 1590169140, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX will launch the second demonstration mission of its Crew Dragon vehicle as part of NASA's Commercial Crew Transportation Capability Program (CCtCap), carrying two NASA astronauts to the International Space Station. Barring unexpected developments, this mission will be the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. DM-2 demonstrates the Falcon 9 and Crew Dragon's ability to safely transport crew to the space station and back to Earth and it is the last major milestone for certification of Crew Dragon. Initially the mission duration was planned to be no longer than two weeks, however NASA has been considering an extension to as much as six weeks or three months. The astronauts have been undergoing additional training for the possible longer mission.", "crew": [ "5ebf1b7323a9a60006e03a7b", "5ebf1a6e23a9a60006e03a7a" ], "ships": [ "5ea6ed30080df4000697c913", "5ea6ed2f080df4000697c90b", "5ea6ed2f080df4000697c90c", "5ea6ed2e080df4000697c909", "5ea6ed2f080df4000697c90d" ], "capsules": [ "5e9e2c5df359188aba3b2676" ], "payloads": [ "5eb0e4d1b6c3bb0006eeb257" ], "launchpad": "5e9e4502f509094188566f88", "auto_update": true, "flight_number": 94, "name": "CCtCap Demo Mission 2", "date_utc": "2020-05-30T19:22:00.000Z", "date_unix": 1590866520, "date_local": "2020-05-30T15:22:00-04:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f3591817f23b2663", "flight": 1, "gridfins": true, "legs": true, "reused": false, "landing_attempt": true, "landing_success": true, "landing_type": "ASDS", "landpad": "5e9e3032383ecb6bb234e7ca" } ], "id": "5eb87d46ffd86e000604b388" } ``` ## Cron Job Status











## Sponsors ### [Studio 3T](https://studio3t.com/) [![Studio 3T](https://imgur.com/DbJSfAo.png)](https://studio3t.com/) ## FAQ's * If you have any questions or corrections, please open an issue and we'll get it merged ASAP * For any other questions or concerns, just shoot me an email. ================================================ FILE: app.js ================================================ import conditional from 'koa-conditional-get'; import etag from 'koa-etag'; import cors from 'koa2-cors'; import dotenv from 'dotenv'; import helmet from 'koa-helmet'; import Koa from 'koa'; import bodyParser from 'koa-bodyparser'; import mongoose from 'mongoose'; import { responseTime, errors, logger } from './middleware/index.js'; import routes from './routes/index.js'; // Env init dotenv.config(); const app = new Koa(); mongoose.connect(process.env.SPACEX_MONGO, { bufferCommands: false, family: 4, }); const db = mongoose.connection; db.on('error', (err) => { logger.error(err); }); db.once('connected', () => { logger.info('Mongo connected'); app.emit('ready'); }); db.on('reconnected', () => { logger.info('Mongo re-connected'); }); db.on('disconnected', () => { logger.info('Mongo disconnected'); }); // disable console.errors for pino app.silent = true; // Error handler app.use(errors); app.use(conditional()); app.use(etag()); app.use(bodyParser()); // HTTP header security app.use(helmet()); // Enable CORS for all routes app.use(cors({ origin: '*', allowMethods: ['GET', 'POST', 'PATCH', 'DELETE'], allowHeaders: ['Content-Type', 'Accept'], exposeHeaders: ['spacex-api-cache', 'spacex-api-response-time'], })); // Set header with API response time app.use(responseTime); // Register routes app.use(await routes()); export default app; ================================================ FILE: docs/README.md ================================================ # r/SpaceX API Docs [![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/ed4ed700dcc55b2c1f1c) ## Disclaimer *We are not affiliated, associated, authorized, endorsed by, or in any way officially connected with Space Exploration Technologies Corp (SpaceX), or any of its subsidiaries or its affiliates. The names SpaceX as well as related names, marks, emblems and images are registered trademarks of their respective owners.* ## Base URL `https://api.spacexdata.com` ## Versioning Each route is individually versioned, see route list below for all avaliable versions. The API can also be pinned to the lastest version with `https://api.spacexdata.com/latest`, but only do this if you're cool with potential breaking changes. ## Authentication Authentication via api key is required for all destructive routes. This includes all `create`, `update`, and `delete` routes. Authenticate by passing the header `spacex-key` with your api key. Protected routes return `401` without a valid key. ## Pagination + Custom Queries All `/query` routes support pagination, custom queries, and other output controls. See the [pagination + query](queries.md) guide for more details and examples. ## Launch date FAQ's * **Why does the date appear wrong?** - This is usually due to the way we store and display partial dates in the api. For example, a launch scheduled for `2020 July` would be represented as `2020-07-01T00:00:00.000Z`. In this case, the field `date_precision` would be set as `month`, meaning the date is only valid to the `month` level, or `2020-07` ## Launch date field explanations * `date_utc` - UTC launch date/time in ISO 8601 format * `date_unix` - UTC launch date/time as a UNIX timestamp in seconds * `date_local` - Local launch time with time zone offset in ISO 8601 format * `date_precision` - Gives the date precision for partial dates. Valid values are `quarter`, `half`, `year`, `month`, `day`, `hour`. * `tbd` - Set as true if date is `To be determined` * `net` - Set as true if the date is `No earlier than` ## Caching The api makes use of response caching via Redis for all `GET` requests, and `POST` requests on `/query` endpoints. Standard cache times are as follows: **launches** - 20 seconds **capsules**, **cores**, **launchpads**, **landpads**, **crew**, **ships**, **payloads** - 5 minutes **dragons**, **rockets** - 24 hours Cache can be cleared with the following endpoint: * 🔒 [Clear cache](cache/clear.md) : `DELETE /admin/cache` ## Routes ### [Capsules](capsules) - Detailed info for serialized dragon capsules ### [Company Info](company) - Detailed info about SpaceX as a company ### [Cores](cores) - Detailed info for serialized first stage cores ### [Crew](crew) - Detailed info on dragon crew members ### [Dragons](dragons) - Detailed info about dragon capsule versions ### [Landpads](landpads) - Detailed info about landing pads and ships ### [Launches](launches) - Detailed info about launches ### [Launchpads](launchpads) - Detailed info about launchpads ### [Payloads](payloads) - Detailed info about launch payloads ### [Roadster info](roadster) - Detailed info about Elon's Tesla roadster's current position ### [Rockets](rockets) - Detailed info about rocket versions ### [Ships](ships) - Detailed info about ships in the SpaceX fleet ### [Starlink](starlink) - Detailed info about Starlink satellites and orbits Includes raw orbit data from [Space Track](https://www.space-track.org/auth/login), updated hourly. Space Track data adheres to the standard for [Orbit Data Messages](https://public.ccsds.org/Pubs/502x0b2c1e2.pdf) ### [History](history) - Detailed info on SpaceX historical events ================================================ FILE: docs/apps.md ================================================ # List of known Apps / UI clients > _Do you, or do you know of some app, that makes use of this community maintained service? If so, please [create an issue](https://github.com/r-spacex/SpaceX-API/issues/new) or [submit a PR](https://github.com/r-spacex/SpaceX-API/pulls) with additions to this list. Thanks_ |App|Type|Creator(s)|Repo|More| |:---|:---|:---|:---|:---| | [2Space](https://play.google.com/store/apps/details?id=abandonedstudio.app.tospace) | Android App | [Pawel Kremienowski](https://github.com/riddick-boss) | [GitHub](https://github.com/riddick-boss/2Space) | | | [SpaceX](http://spacex.elc0mpa.me/#/) | Website | [elC0mpa](https://github.com/elC0mpa) | [GitHub](https://github.com/elC0mpa/spacex) | built with Vue3 & Composition API | | [SpaceX History](https://spacex-history.netlify.app) | Website | [Bedirhan Karadoğan](https://github.com/bedirhankaradogan) | [GitHub](https://github.com/bedirhankaradogan/spacex-history) | built with React | | [🚀 SpaceX CLI](https://www.npmjs.com/package/@orhanemree/spacex-cli) | CLI Tool | [Orhan Emre Dikicigil](https://github.com/orhanemree) | [GitHub](https://github.com/orhanemree/spacex-cli) | built with Node | | [SpaceX Live Info 🔴️](https://spacex-live.netlify.app/) | Website | [Pawel Pisulski](https://github.com/pislagz) | [GitHub](https://github.com/pislagz/spacex-live) | built with React & Redux | | [SpaceXtale](https://tsensei.github.io/spaceXtale/) | Website | [tsensei](https://github.com/tsensei/) | [GitHub](https://github.com/tsensei/spaceXtale) | React app with spaceX-api | | [Rocket Downrange](https://rocketdownrange.com) | Website | [Cavan Lemasters](https://github.com/TheKicker) | [GitHub](https://github.com/TheKicker/rocket-downrange) | | | [SpaceX-Dashboard🚀](https://thespacexdashboard.netlify.app/) | Website | [Sanjay Rajesh](https://github.com/sanjayrjs16) | [GitHub](https://github.com/sanjayrjs16/spaceX-dashboard-react)|| | [SpaceX-Launches](https://spacex.prutkowski.tech/) | Website | [Piotr Rutkowski](https://github.com/PiotrRut) | [GitHub](https://github.com/PiotrRut/SpaceX-Launches) | | | [SpaceX and Mars](https://www.spacexandmars.com/) | Website | [Jiacheng Zhang](https://github.com/jiachengzhang1) | [GitHub](https://github.com/jiachengzhang1/spacex-and-mars) | | | [When is the next SpaceX launch](https://whenisthenextspacexlaunch.com) | Website | [Warwick Ward](https://warwick.io) | [GitHub](https://github.com/warwickofthegh/whenisthenextspacexlaunch.com) | | | [SpaceX Mission Watch](https://spacexmissionwatch.com) | Website | Quent McCoy | | [QMDD](https://quentmccoy.com) | | [SpaceX Stats](http://spacexstats.xyz) | Website | Luke Davia | [GitHub](https://github.com/r-spacex/spacexstats-react) | | [SpaceX Data.Info](http://spacexdata.info) | Website | [Charles Omer](https://www.charlesomer.co.uk) | | [Zyndex](https://www.zyndex.co.uk) | | [SpaceX Wiki](https://www.spacexwiki.com/) | Website | [Chris Stielper](https://github.com/cstielper) | [GitHub](https://github.com/cstielper/react-spacex-wiki) | | [X-Watch](https://x-watch.xyz/) | Website | [Matt Mills](https://github.com/mattmillsxyz) | [GitHub](https://github.com/mattmillsxyz/x-watch) | | [SpaceXLaunches.com](https://spacexlaunches.com) | Website | [louisjc](https://github.com/louisjc/) | [GitHub](https://github.com/louisjc/spacexlaunches.com) | | [SpaceFax](https://spacefax1.web.app) | Website | [James Hendrie](https://github.com/jimmyboix) | [GitHub](https://github.com/jimmyboix/SpaceFax) | | | [Deploy to Space](https://spacex-fs.deployto.space/) | Website | [Yannick Durden](https://github.com/YannickDurden) | | | | [SpaceX Track](https://www.spacextrack.com/) | Website | [Joe Dineen](https://github.com/jdineen21) | [GitHub](https://github.com/jdineen21/space_django) | [Portfolio](https://www.jdineen.co.uk) | | [SpaceX Guide Site](https://spacex-guide.weebly.com) | Website | Jared-Base | | [Mission Control Page](https://spacex-guide.weebly.com/mission-control.html) | | [SpaceX info Site](https://infospacex.vercel.app/) | Website | [Tigran Mkrtchyan](https://github.com/mkrtchyan98) | [Github](https://github.com/mkrtchyan98/SpaceInfo) | | [spacexdash](https://spacexdash.github.io/x) | Website | [spacexdash](https://github.com/spacexdash) | [Github](https://github.com/spacexdash/x/tree/master) | Easy searching of SpaceX data | | [x-info](https://x-info.eu) | Website | [Patryk Wojcieszak](https://github.com/PatrykWojcieszak) | [GitHub](https://github.com/PatrykWojcieszak/X-Info) | | | [SpaceX Launch Tracker](https://www.spacexlaunchtracker.com/) | Website | [Emil Sadek](https://github.com/esadek) | [GitHub](https://github.com/esadek/spacex-launch-tracker) | | | [SpX](https://apps.apple.com/gb/app/spx/id1511355787) | iOS App | [Russell Warwick](https://github.com/waruss321) | | | [XLaunch](https://apps.apple.com/us/app/xlaunch/id1502939751) | iOS App | [Travis Stanifer](https://github.com/stanifert) | | | [SpaceXPedia](https://itunes.apple.com/app/spacexpedia/id1434177600?mt=8) | iOS App | [Philip Engberg](https://github.com/philipengberg) | [GitHub](https://github.com/philipengberg/SpaceXPedia) | | [SpaceDash](https://apps.apple.com/in/app/space-dash/id1527766640) | iOS App | [Pushpinder Pal Singh](https://github.com/pushpinderpalsingh) | [GitHub](https://github.com/pushpinderpalsingh/SpaceDash) | | [Space Xploration](https://apps.apple.com/app/space-xploration/id1530580909) | iOS App | [Bartolome Estelrich](https://github.com/BEstelrich) | | | | [SpacExtra](https://apps.apple.com/ca/app/spacextra/id1559360281) | iOS App | [Charlie Kingsland](https://github.com/ChopsKingsland) | | [Website](https://spacextra.github.io) | | [Norminal](https://apps.apple.com/app/norminal/id1540171547) | iOS App | [Riccardo Persello](https://github.com/persello) | [GitHub](https://github.com/persello/norminal) | | | [SpaceX Tracker](https://play.google.com/store/apps/details?id=com.magnetar.spacexlauncher) | Android App | [Timo Nelen](https://github.com/TNelen) | [Github](https://github.com/TNelen/SpacexLaunchApp) | | | [SpaceX Companion](https://play.google.com/store/apps/details?id=nl.studionoorderlicht.spacex) | Android App | [Jeroen Boumans](https://github.com/jeroenboumans) | Not open sourced yet | [Website](https://spacexcompanion.app) | [SpaceXLaunches](https://play.google.com/store/apps/details?id=com.danielscholte.spacexlaunches) | Android App | [Daniël Scholte](https://github.com/linuxfreak23) | Closed source | | [SpaceX GO!](https://play.google.com/store/apps/details?id=com.chechu.cherry) | Android App | [Chechu](https://github.com/jesusrp98) | [GitHub](https://github.com/jesusrp98/spacex-go) | | [SpaceX Universe](https://play.google.com/store/apps/details?id=com.aastudio.spacexuniverse) | Android App | [Oleksii Chernov](https://github.com/chert12) | [Github](https://github.com/chert12/SpaceX-Universe) | | | [SpaceXFollower](https://github.com/OMIsie11/SpaceXFollower) | Android App | [Oskar Misiewicz @OMIsie11](https://omisie11.github.io) | [Github](https://github.com/OMIsie11/SpaceXFollower) | | | [Spacexopedia](https://play.google.com/store/apps/details?id=com.thealphamerc.flutter_spacexopedia) | Android App | [Sonu Sharma](https://github.com/TheAlphamerc) | [GitHub](https://github.com/TheAlphamerc/flutter_spacexopedia) | | | [SpaceX Wiki](https://github.com/prasannajeet/SpaceX_Wiki_KMM_iOS_Android) | Android & iOS App | [Prasan @prasannajeet](https://github.com/prasannajeet) | [Github](https://github.com/prasannajeet/SpaceX_Wiki_KMM_iOS_Android) | Built with Kotlin Multiplatform Mobile | | [Launchpad](https://github.com/skyffx/Launchpad/releases) | Windows App | [Wojciech Piekielniak](https://github.com/skyffx/) | [Github](https://github.com/skyffx/Launchpad) | | [InElonWeTrust](https://github.com/Tearth/InElonWeTrust) | Discord bot | [Tearth](https://github.com/Tearth) | [GitHub](https://github.com/Tearth/InElonWeTrust) | | [SpaceX-Launch-Bot](https://github.com/SpaceXLaunchBot/SpaceXLaunchBot) | Discord bot | [Simon](https://github.com/psidex) | [Github](https://github.com/SpaceXLaunchBot/SpaceXLaunchBot) | | | [Space Launch Now](https://spacelaunchnow.me/) | API/Website/Apps | [Caleb Jones](https://github.com/ItsCalebJones) | [Github](https://github.com/ItsCalebJones/SpaceLaunchNow-Server) | | | [AV-SpaceX DB](https://av-spacex.surge.sh/) | API/Website | [Aswin Varghese](http://aswinvarghese.com) | [Github]() | | | SpaceX Info | Alexa Skill | Andrew Shapton | [Github](https://github.com/alshapton/Space-X-Info-Alexa.git) | Invoke with "Alexa Open SpaceX Info" | | [SpaceX - Launch Tracker](https://play.google.com/store/apps/details?id=uk.co.zac_h.spacex) | Android App | [Zac Hadjineophytou](https://github.com/zacdevil10) | [GitHub](https://github.com/zacdevil10/spacex-launch-tracker) | | [Space Bot](https://t.me/spacex_mezgoodle_bot) | Telegram Bot | [Maxim Zavalniuk](https://github.com/mezgoodle) | [GitHub](https://github.com/mezgoodle/space-bot) | | [SpaceX Launch Data](http://spacexlaunchdata.com) | Website | [Sam Girshovich](https://github.com/samg11) | [GitHub](https://github.com/samg11/SpaceX-Launch-Data) | | [SpaceX Times](https://play.google.com/store/apps/details?id=ru.alexmaryin.spacextimes_rx) | Android App | [Alex Maryin](https://github.com/alexmaryin) | [GitHub](https://github.com/alexmaryin/spacextimes) | | [SpaceX Missions](https://spacex-missions.netlify.app) | Website | [Bruno Porcel](https://github.com/bporcel) | [GitHub](https://github.com/bporcel/Space-X) | | [Launch Tracker - SpaceX](https://play.google.com/store/apps/details?id=com.bvdwalt.spacex_flights) | Android App | [Bennie van der Walt](https://github.com/bvdwalt) | [GitHub](https://github.com/bvdwalt/Launch-Tracker-SpaceX) | [SpaceX Dashboard](https://tdunn891.github.io/spacex-dashboard/) | Website | [Thomas Dunn](https://github.com/tdunn891) | [GitHub](https://github.com/tdunn891/spacex-dashboard) | | | [SpaceX Launches](https://spacexlaunches.github.io/) | Website | [l3ycle](https://github.com/l3ycle) | [GitHub](https://github.com/spacexlaunches/spacexlaunches.github.io/) | | | [SpaceX-Launches](https://amazing-austin-1853eb.netlify.app) 🚀 | Website | [Atamyrat Abdyrahmanov](https://github.com/aaabdyrahmanov) | [GitHub](https://github.com/aaabdyrahmanov/SpaceX-Launches) | | | [SpaceX history timeline](https://www.orbitaterrestre.com/la-storia-di-spaceX-in-timeline-component) | Website | [Vincenzo](https://github.com/vincenzomarcovecchio) | [GitHub](https://github.com/aaabdyrahmanov/SpaceX-Launches) | | | [Elon Musk Tracker](https://play.google.com/store/apps/details?id=com.ingenuity.elonmusktracker) | Android/iOS App | [Aman Trifi](https://github.com/TrifiAmanallah) | Closed source | | | [SpaceX Data](https://spacexdata.pages.dev/) | Website | [Chien Tran](https://github.com/chientrm) | Closed source | Built with SvelteKit, deployed with Cloudflare Pages | | [SpaceX Portal](https://spacex-portal.vercel.app) | Website | [Daniel Yasakov](https://github.com/ne-danik) | [GitHub](https://github.com/ne-danik/spacex-portal) | built with React | ================================================ FILE: docs/capsules/v4/all.md ================================================ # Get all capsules **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/capsules` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "reuse_count": 1, "water_landings": 1, "land_landings": 0, "last_update": "Reentered after three weeks in orbit", "launches": [ "5eb87cdeffd86e000604b330" ], "serial": "C101", "status": "retired", "type": "Dragon 1.0", "id": "5e9e2c5bf35918ed873b2664" }, ... ] ``` ================================================ FILE: docs/capsules/v4/one.md ================================================ # Get one capsule **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/capsules/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the capsule **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "reuse_count": 1, "water_landings": 1, "land_landings": 0, "last_update": "Reentered after three weeks in orbit", "launches": [ "5eb87cdeffd86e000604b330" ], "serial": "C101", "status": "retired", "type": "Dragon 1.0", "id": "5e9e2c5bf35918ed873b2664" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/capsules/v4/query.md ================================================ # Query capsules **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/capsules/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "reuse_count": 1, "water_landings": 1, "land_landings": 0, "last_update": "Reentered after three weeks in orbit", "launches": [ "5eb87cdeffd86e000604b330" ], "serial": "C101", "status": "retired", "type": "Dragon 1.0", "id": "5e9e2c5bf35918ed873b2664" }, ... ], "totalDocs": 19, "offset": 0, "limit": 10, "totalPages": 2, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": true, "prevPage": null, "nextPage": 2 } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/capsules/v4/schema.md ================================================ # Capsule Schema ```json { "serial": { "type": "String", "required": true, "unique": true, }, "status": { "type": "String", "enum": ["unknown", "active", "retired", "destroyed"], "required": true, }, "type": { "type": "String", "enum": ["Dragon 1.0", "Dragon 1.1", "Dragon 2.0"], "required": true, }, "dragon": { "type": "UUID", }, "reuse_count": { "type": "Number", "default": 0, }, "water_landings": { "type": "Number", "default": 0, }, "land_landings": { "type": "Number", "default": 0, }, "last_update": { "type": "String", "default": null, }, "launches": [{ "type": "UUID", }], } ``` ================================================ FILE: docs/clients.md ================================================ # List of known API clients / wrappers > _Do you, or do you know of some client/wrapper, that makes use of this community maintained service? If so, please [create an issue](https://github.com/r-spacex/SpaceX-API/issues/new) or [submit a PR](https://github.com/r-spacex/SpaceX-API/pulls) with additions to this list. Thanks_ NOTE: Clients are grouped by API version(s) supported ### V4 (Latest) |Name|Lang|Creator(s)|Repo| |:---|:---|:---|:---| | Oddity | .NET | Tearth | [GitHub](https://github.com/Tearth/Oddity) | | SpaceXPy | Python | Ryu JuHeon | [GitHub](https://github.com/SaidBySolo/SpaceXPy) | | KSBSpacexKit | Swift | SaiBalaji K| [GitHub](https://github.com/SaiBalaji22/KSBSpacexKit) | | Marsy | C++ | AzuxDario | [GitHub](https://github.com/AzuxDario/Marsy) | | Spacex-api.js | Node.js | AkiaCode | [Github](https://github.com/AkiaCode/spacex-api.js) | | spacex_api | Dart | Ahsanz024 | [Github](https://github.com/ahsanz024/spacex_api) | | spacex_api | Ruby | Victor Perez | [Github](https://github.com/victorperez/spacex-api-ruby) | | xploration-graphql | TypeScript | Kartik Kumar Gujarati | [Github](https://github.com/Kartikkumargujarati/xploration-graphql) | | spacex-graphql-gateway | TypeScript | Kevin Hermawan | [Github](https://github.com/kevinstd/spacex-graphql-gateway) | | r/SpaceX (Independent Publisher) | [Power Platform](https://docs.microsoft.com/en-us/connectors/rspacexip/) | Troy Taylor | [Github](https://github.com/troystaylor/PowerPlatformConnectors/tree/r/SpaceX/independent-publisher-connectors/rSpaceX) | | spacex-api | Java | Andrey Vasilyev | [Github](https://github.com/artfultom/spacex-api) | ### V3, V2, V1 (Deprecated) |Name|Lang|Creator(s)|Repo| |:---|:---|:---|:---| | SpaceX-GraphQL | TypeScript | Jordan Owens | [GitHub](https://github.com/jor-dan/SpaceX-GraphQL) | | spacex-graphql-api | GraphQL | Emerson Laurentino | [GitHub](https://github.com/emersonlaurentino/spacex-qraphql-api) | | SpaceX-API-Wrapper | Node.js | Thomas Smyth | [GitHub](https://github.com/Thomas-Smyth/SpaceX-API-Wrapper) | | spacex-api | TypeScript / Node.js | Tomasz Borowski | [GitHub](https://github.com/tbprojects/spacex-api), [NPM](https://www.npmjs.com/package/spacex-api) | | SpaceX | PowerShell | François-Xavier Cat | [GitHub](https://github.com/lazywinadmin/SpaceX) | | SpacePY-X | Python | Andrew Shapton | [GitHub](https://github.com/alshapton/SpacePY-X) | | SpaceX-PY | Python | Kaylum Lally | [GitHub](https://github.com/HiKaylum/SpaceX-PY) | | SpaceNIM-X | Nim | Andrew Shapton | [GitHub](https://github.com/alshapton/SpaceNIM-X) | | SpaceCRYST-X | Crystal | Andrew Shapton | [GitHub](https://github.com/alshapton/SpaceCRYST-X) | | spacex | Go | Or Hiltch | [GitHub](https://github.com/orcaman/spacex) | | spacex-api-wrapper | Rust | Alex Gutan | [GitHub](https://github.com/AGutan/spacex-api-wrapper)| | spacex-graphql-rust | Rust | Aaron Feigenbaum | [GitHub](https://github.com/adace123/spacex-graphql-rust)| | space-rx | Rust | Tyler Wilcock | [GitHub](https://github.com/twilco/space-rx) | | spacex | Ruby | Rodolfo | [GitHub](https://github.com/rodolfobandeira/spacex) | | spacex | PHP | Aires Gonçalves | [GitHub](https://github.com/airesvsg/spacex) | | spacex_ex | Elixir | Chen Zhao | [GitHub](https://github.com/crunchysoul/spacex_ex) | | SpaceX | R | Johannes Friedrich | [GitHub](https://github.com/JohannesFriedrich/SpaceX) | | SpaceXAPI-Swift | Swift | Sami Sharafeddine | [GitHub](https://github.com/devsamsh/SpaceXAPI-Swift) | ================================================ FILE: docs/company/v4/all.md ================================================ # Get all company info **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/company` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json { "headquarters": { "address": "Rocket Road", "city": "Hawthorne", "state": "California" }, "links": { "website": "https://www.spacex.com/", "flickr": "https://www.flickr.com/photos/spacex/", "twitter": "https://twitter.com/SpaceX", "elon_twitter": "https://twitter.com/elonmusk" }, "name": "SpaceX", "founder": "Elon Musk", "founded": 2002, "employees": 8000, "vehicles": 3, "launch_sites": 3, "test_sites": 1, "ceo": "Elon Musk", "cto": "Elon Musk", "coo": "Gwynne Shotwell", "cto_propulsion": "Tom Mueller", "valuation": 52000000000, "summary": "SpaceX designs, manufactures and launches advanced rockets and spacecraft. The company was founded in 2002 to revolutionize space technology, with the ultimate goal of enabling people to live on other planets.", "id": "5eb75edc42fea42237d7f3ed" } ``` ================================================ FILE: docs/company/v4/schema.md ================================================ # Company Info Schema ```json { "name": { "type": "String" }, "founder": { "type": "String" }, "founded": { "type": "Number" }, "employees": { "type": "Number" }, "vehicles": { "type": "Number" }, "launch_sites": { "type": "Number" }, "test_sites": { "type": "Number" }, "ceo": { "type": "String" }, "cto": { "type": "String" }, "coo": { "type": "String" }, "cto_propulsion": { "type": "String" }, "valuation": { "type": "Number" }, "headquarters": { "address": { "type": "String" }, "city": { "type": "String" }, "state": { "type": "String" } }, "links": { "website": { "type": "String" }, "flickr": { "type": "String" }, "twitter": { "type": "String" }, "elon_twitter": { "type": "String" } }, "summary": { "type": "String" } } ``` ================================================ FILE: docs/cores/v4/all.md ================================================ # Get all cores **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/cores` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "block": 5, "reuse_count": 3, "rtls_attempts": 1, "rtls_landings": 1, "asds_attempts": 3, "asds_landings": 3, "last_update": "Landed on OCISLY as of Jan 29, 2020. ", "launches": [ "5eb87d2bffd86e000604b375", "5eb87d31ffd86e000604b379", "5eb87d3fffd86e000604b382", "5eb87d44ffd86e000604b386" ], "serial": "B1051", "status": "active", "id": "5e9e28a6f35918c0803b265c" }, ... ] ``` ================================================ FILE: docs/cores/v4/one.md ================================================ # Get one core **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/cores/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the core **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "block": 5, "reuse_count": 3, "rtls_attempts": 1, "rtls_landings": 1, "asds_attempts": 3, "asds_landings": 3, "last_update": "Landed on OCISLY as of Jan 29, 2020. ", "launches": [ "5eb87d2bffd86e000604b375", "5eb87d31ffd86e000604b379", "5eb87d3fffd86e000604b382", "5eb87d44ffd86e000604b386" ], "serial": "B1051", "status": "active", "id": "5e9e28a6f35918c0803b265c" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/cores/v4/query.md ================================================ # Query cores **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/cores/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "block": 5, "reuse_count": 3, "rtls_attempts": 1, "rtls_landings": 1, "asds_attempts": 2, "asds_landings": 2, "last_update": "Missed the droneship and made successful water landing; apparently scuttled at sea afterward. ", "launches": [ "5eb87d2effd86e000604b377", "5eb87d36ffd86e000604b37b", "5eb87d3bffd86e000604b37f", "5eb87d41ffd86e000604b383" ], "serial": "B1056", "status": "lost", "id": "5e9e28a7f3591809313b2660" }, ... ], "totalDocs": 65, "offset": 0, "limit": 10, "totalPages": 7, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": true, "prevPage": null, "nextPage": 2 } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/cores/v4/schema.md ================================================ # Core Schema ```json { "serial": { "type": "String", "unique": true, "required": true, }, "block": { "type": "Number", "default": null, }, "status": { "type": "String", "enum": ["active", "inactive", "unknown", "expended", "lost", "retired"], "required": true, }, "reuse_count": { "type": "Number", "default": 0, }, "rtls_attempts": { "type": "Number", "default": 0, }, "rtls_landings": { "type": "Number", "default": 0, }, "asds_attempts": { "type": "Number", "default": 0, }, "asds_landings": { "type": "Number", "default": 0, }, "last_update": { "type": "String", "default": null, }, "launches": [{ "type": "UUID", }], } ``` ================================================ FILE: docs/crew/v4/all.md ================================================ # Get all crew **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/crew` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "name": "Robert Behnken", "agency": "NASA", "image": "https://imgur.com/0smMgMH.png", "wikipedia": "https://en.wikipedia.org/wiki/Robert_L._Behnken", "launches": [ "5eb87d46ffd86e000604b388" ], "status": "active", "id": "5ebf1a6e23a9a60006e03a7a" }, ... ] ``` ================================================ FILE: docs/crew/v4/one.md ================================================ # Get one crew member **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/crew/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the crew member **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "name": "Douglas Hurley", "agency": "NASA", "image": "https://i.imgur.com/ooaayWf.png", "wikipedia": "https://en.wikipedia.org/wiki/Douglas_G._Hurley", "launches": [ "5eb87d46ffd86e000604b388" ], "status": "active", "id": "5ebf1b7323a9a60006e03a7b" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/crew/v4/query.md ================================================ # Query crew members **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/crew/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "name": "Robert Behnken", "agency": "NASA", "image": "https://imgur.com/0smMgMH.png", "wikipedia": "https://en.wikipedia.org/wiki/Robert_L._Behnken", "launches": [ "5eb87d46ffd86e000604b388" ], "status": "active", "id": "5ebf1a6e23a9a60006e03a7a" }, ... ], "totalDocs": 2, "offset": 0, "limit": 10, "totalPages": 1, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": false, "prevPage": null, "nextPage": null } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/crew/v4/schema.md ================================================ # Crew Schema ```json { "name": { "type": "String", "default": null }, "status": { "type": "String", "required": true, "enum": ["active", "inactive", "retired", "unknown"] }, "agency": { "type": "String", "default": null }, "image": { "type": "String", "default": null }, "wikipedia": { "type": "String", "default": null }, "launches": [{ "type": "UUID" }] } ``` ================================================ FILE: docs/dragons/v4/all.md ================================================ # Get all Dragons **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/dragons` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "heat_shield": { "material": "PICA-X", "size_meters": 3.6, "temp_degrees": 3000, "dev_partner": "NASA" }, "launch_payload_mass": { "kg": 6000, "lb": 13228 }, "launch_payload_vol": { "cubic_meters": 25, "cubic_feet": 883 }, "return_payload_mass": { "kg": 3000, "lb": 6614 }, "return_payload_vol": { "cubic_meters": 11, "cubic_feet": 388 }, "pressurized_capsule": { "payload_volume": { "cubic_meters": 11, "cubic_feet": 388 } }, "trunk": { "trunk_volume": { "cubic_meters": 14, "cubic_feet": 494 }, "cargo": { "solar_array": 2, "unpressurized_cargo": true } }, "height_w_trunk": { "meters": 7.2, "feet": 23.6 }, "diameter": { "meters": 3.7, "feet": 12 }, "first_flight": "2010-12-08", "flickr_images": [ "https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2015_-_04_crs5_dragon_orbit13.jpg?itok=9p8_l7UP", "https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2012_-_4_dragon_grapple_cots2-1.jpg?itok=R2-SeuMX", "https://farm3.staticflickr.com/2815/32761844973_4b55b27d3c_b.jpg", "https://farm9.staticflickr.com/8618/16649075267_d18cbb4342_b.jpg" ], "name": "Dragon 1", "type": "capsule", "active": true, "crew_capacity": 0, "sidewall_angle_deg": 15, "orbit_duration_yr": 2, "dry_mass_kg": 4200, "dry_mass_lb": 9300, "thrusters": [ { "type": "Draco", "amount": 18, "pods": 4, "fuel_1": "nitrogen tetroxide", "fuel_2": "monomethylhydrazine", "isp": 300, "thrust": { "kN": 0.4, "lbf": 90 } } ], "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_Dragon", "description": "Dragon is a reusable spacecraft developed by SpaceX, an American private space transportation company based in Hawthorne, California. Dragon is launched into space by the SpaceX Falcon 9 two-stage-to-orbit launch vehicle. The Dragon spacecraft was originally designed for human travel, but so far has only been used to deliver cargo to the International Space Station (ISS).", "id": "5e9d058759b1ff74a7ad5f8f" }, ... ] ``` ================================================ FILE: docs/dragons/v4/one.md ================================================ # Get one Dragon **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/dragons/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the dragon **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "heat_shield": { "material": "PICA-X", "size_meters": 3.6, "temp_degrees": 3000, "dev_partner": "NASA" }, "launch_payload_mass": { "kg": 6000, "lb": 13228 }, "launch_payload_vol": { "cubic_meters": 25, "cubic_feet": 883 }, "return_payload_mass": { "kg": 3000, "lb": 6614 }, "return_payload_vol": { "cubic_meters": 11, "cubic_feet": 388 }, "pressurized_capsule": { "payload_volume": { "cubic_meters": 11, "cubic_feet": 388 } }, "trunk": { "trunk_volume": { "cubic_meters": 14, "cubic_feet": 494 }, "cargo": { "solar_array": 2, "unpressurized_cargo": true } }, "height_w_trunk": { "meters": 7.2, "feet": 23.6 }, "diameter": { "meters": 3.7, "feet": 12 }, "first_flight": "2010-12-08", "flickr_images": [ "https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2015_-_04_crs5_dragon_orbit13.jpg?itok=9p8_l7UP", "https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2012_-_4_dragon_grapple_cots2-1.jpg?itok=R2-SeuMX", "https://farm3.staticflickr.com/2815/32761844973_4b55b27d3c_b.jpg", "https://farm9.staticflickr.com/8618/16649075267_d18cbb4342_b.jpg" ], "name": "Dragon 1", "type": "capsule", "active": true, "crew_capacity": 0, "sidewall_angle_deg": 15, "orbit_duration_yr": 2, "dry_mass_kg": 4200, "dry_mass_lb": 9300, "thrusters": [ { "type": "Draco", "amount": 18, "pods": 4, "fuel_1": "nitrogen tetroxide", "fuel_2": "monomethylhydrazine", "isp": 300, "thrust": { "kN": 0.4, "lbf": 90 } } ], "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_Dragon", "description": "Dragon is a reusable spacecraft developed by SpaceX, an American private space transportation company based in Hawthorne, California. Dragon is launched into space by the SpaceX Falcon 9 two-stage-to-orbit launch vehicle. The Dragon spacecraft was originally designed for human travel, but so far has only been used to deliver cargo to the International Space Station (ISS).", "id": "5e9d058759b1ff74a7ad5f8f" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/dragons/v4/query.md ================================================ # Query Dragons **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/dragons/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "heat_shield": { "material": "PICA-X", "size_meters": 3.6, "temp_degrees": 3000, "dev_partner": "NASA" }, "launch_payload_mass": { "kg": 6000, "lb": 13228 }, "launch_payload_vol": { "cubic_meters": 25, "cubic_feet": 883 }, "return_payload_mass": { "kg": 3000, "lb": 6614 }, "return_payload_vol": { "cubic_meters": 11, "cubic_feet": 388 }, "pressurized_capsule": { "payload_volume": { "cubic_meters": 11, "cubic_feet": 388 } }, "trunk": { "trunk_volume": { "cubic_meters": 14, "cubic_feet": 494 }, "cargo": { "solar_array": 2, "unpressurized_cargo": true } }, "height_w_trunk": { "meters": 7.2, "feet": 23.6 }, "diameter": { "meters": 3.7, "feet": 12 }, "first_flight": "2010-12-08", "flickr_images": [ "https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2015_-_04_crs5_dragon_orbit13.jpg?itok=9p8_l7UP", "https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2012_-_4_dragon_grapple_cots2-1.jpg?itok=R2-SeuMX", "https://farm3.staticflickr.com/2815/32761844973_4b55b27d3c_b.jpg", "https://farm9.staticflickr.com/8618/16649075267_d18cbb4342_b.jpg" ], "name": "Dragon 1", "type": "capsule", "active": true, "crew_capacity": 0, "sidewall_angle_deg": 15, "orbit_duration_yr": 2, "dry_mass_kg": 4200, "dry_mass_lb": 9300, "thrusters": [ { "type": "Draco", "amount": 18, "pods": 4, "fuel_1": "nitrogen tetroxide", "fuel_2": "monomethylhydrazine", "isp": 300, "thrust": { "kN": 0.4, "lbf": 90 } } ], "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_Dragon", "description": "Dragon is a reusable spacecraft developed by SpaceX, an American private space transportation company based in Hawthorne, California. Dragon is launched into space by the SpaceX Falcon 9 two-stage-to-orbit launch vehicle. The Dragon spacecraft was originally designed for human travel, but so far has only been used to deliver cargo to the International Space Station (ISS).", "id": "5e9d058759b1ff74a7ad5f8f" }, ... ], "totalDocs": 2, "offset": 0, "limit": 10, "totalPages": 1, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": false, "prevPage": null, "nextPage": null } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/dragons/v4/schema.md ================================================ # Dragon Schema ```json { "name": { "type": "String", "unique": true, "required": true }, "type": { "type": "String", "required": true }, "active": { "type": "Boolean", "required": true }, "crew_capacity": { "type": "Number", "required": true }, "sidewall_angle_deg": { "type": "Number", "required": true }, "orbit_duration_yr": { "type": "Number", "required": true }, "dry_mass_kg": { "type": "Number", "required": true }, "dry_mass_lb": { "type": "Number", "required": true }, "first_flight": { "type": "String", "default": null }, "heat_shield": { "material": { "type": "String", "required": true }, "size_meters": { "type": "Number", "required": true }, "temp_degrees": { "type": "Number" }, "dev_partner": { "type": "String" } }, "thrusters": { "type": "Object" }, "launch_payload_mass": { "kg": { "type": "Number" }, "lb": { "type": "Number" } }, "launch_payload_vol": { "cubic_meters": { "type": "Number" }, "cubic_feet": { "type": "Number" } }, "return_payload_mass": { "kg": { "type": "Number" }, "lb": { "type": "Number" } }, "return_payload_vol": { "cubic_meters": { "type": "Number" }, "cubic_feet": { "type": "Number" } }, "pressurized_capsule": { "payload_volume": { "cubic_meters": { "type": "Number" }, "cubic_feet": { "type": "Number" } } }, "trunk": { "trunk_volume": { "cubic_meters": { "type": "Number" }, "cubic_feet": { "type": "Number" } }, "cargo": { "solar_array": { "type": "Number" }, "unpressurized_cargo": { "type": "Boolean" } } }, "height_w_trunk": { "meters": { "type": "Number" }, "feet": { "type": "Number" } }, "diameter": { "meters": { "type": "Number" }, "feet": { "type": "Number" } }, "flickr_images": { "type": [ "String" ] }, "wikipedia": { "type": "String" }, "description": { "type": "String" } } ``` ================================================ FILE: docs/history/v4/all.md ================================================ # Get all history events **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/history` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "title": "SpaceX successfully launches humans to ISS", "event_date_utc": "2020-05-30T19:22:00Z", "event_date_unix": 1590866520, "details": "This mission was the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. It carried NASA astronauts Doug Hurley and Bob Behnken to the ISS.", "links": { "article": "https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/" } } ... ] ``` ================================================ FILE: docs/history/v4/one.md ================================================ # Get one history event **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/history/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the history event **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "title": "SpaceX successfully launches humans to ISS", "event_date_utc": "2020-05-30T19:22:00Z", "event_date_unix": 1590866520, "details": "This mission was the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. It carried NASA astronauts Doug Hurley and Bob Behnken to the ISS.", "links": { "article": "https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/" } } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/history/v4/query.md ================================================ # Query history events **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/history/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "title": "SpaceX successfully launches humans to ISS", "event_date_utc": "2020-05-30T19:22:00Z", "event_date_unix": 1590866520, "details": "This mission was the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. It carried NASA astronauts Doug Hurley and Bob Behnken to the ISS.", "links": { "article": "https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/" } } ... ], "totalDocs": 7, "offset": 0, "limit": 10, "totalPages": 1, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": false, "prevPage": null, "nextPage": null } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/history/v4/schema.md ================================================ # History Event Schema ```json { "title": { "type": "String", "default": null, }, "event_date_utc": { "type": "String", "default": null, }, "event_date_unix": { "type": "Number", "default": null, }, "details": { "type": "String", "default": null, }, "links": { "article": { "type": "String", "default": null, }, }, } ``` ================================================ FILE: docs/landpads/v4/all.md ================================================ # Get all landing pads **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/landpads` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "name": "LZ-2", "full_name": "Landing Zone 2", "status": "active", "type": "RTLS", "locality": "Cape Canaveral", "region": "Florida", "latitude": 28.485833, "longitude": -80.544444, "landing_attempts": 3, "landing_successes": 3, "wikipedia": "https://en.wikipedia.org/wiki/Landing_Zones_1_and_2", "details": "SpaceX's first east coast landing pad is Landing Zone 1, where the historic first Falcon 9 landing occurred in December 2015. LC-13 was originally used as a launch pad for early Atlas missiles and rockets from Lockheed Martin. LC-1 was later expanded to include Landing Zone 2 for side booster RTLS Falcon Heavy missions, and it was first used in February 2018 for that purpose.", "launches": [ "5eb87d13ffd86e000604b360", "5eb87d2dffd86e000604b376", "5eb87d35ffd86e000604b37a" ], "id": "5e9e3032383ecb90a834e7c8" }, ... ] ``` ================================================ FILE: docs/landpads/v4/one.md ================================================ # Get one landing pad **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/landpads/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the landing pad **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "name": "LZ-2", "full_name": "Landing Zone 2", "status": "active", "type": "RTLS", "locality": "Cape Canaveral", "region": "Florida", "latitude": 28.485833, "longitude": -80.544444, "landing_attempts": 3, "landing_successes": 3, "wikipedia": "https://en.wikipedia.org/wiki/Landing_Zones_1_and_2", "details": "SpaceX's first east coast landing pad is Landing Zone 1, where the historic first Falcon 9 landing occurred in December 2015. LC-13 was originally used as a launch pad for early Atlas missiles and rockets from Lockheed Martin. LC-1 was later expanded to include Landing Zone 2 for side booster RTLS Falcon Heavy missions, and it was first used in February 2018 for that purpose.", "launches": [ "5eb87d13ffd86e000604b360", "5eb87d2dffd86e000604b376", "5eb87d35ffd86e000604b37a" ], "id": "5e9e3032383ecb90a834e7c8" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/landpads/v4/query.md ================================================ # Query landing pads **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/landpads/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "name": "LZ-1", "full_name": "Landing Zone 1", "status": "active", "type": "RTLS", "locality": "Cape Canaveral", "region": "Florida", "latitude": 28.485833, "longitude": -80.544444, "landing_attempts": 15, "landing_successes": 14, "wikipedia": "https://en.wikipedia.org/wiki/Landing_Zones_1_and_2", "details": "SpaceX's first east coast landing pad is Landing Zone 1, where the historic first Falcon 9 landing occurred in December 2015. LC-13 was originally used as a launch pad for early Atlas missiles and rockets from Lockheed Martin. LC-1 was later expanded to include Landing Zone 2 for side booster RTLS Falcon Heavy missions, and it was first used in February 2018 for that purpose.", "launches": [ "5eb87cefffd86e000604b342", "5eb87cf9ffd86e000604b349", "5eb87cfeffd86e000604b34d", "5eb87d01ffd86e000604b350", "5eb87d03ffd86e000604b352", "5eb87d07ffd86e000604b356", "5eb87d09ffd86e000604b358", "5eb87d0effd86e000604b35c", "5eb87d10ffd86e000604b35e", "5eb87d13ffd86e000604b360", "5eb87d26ffd86e000604b371", "5eb87d2dffd86e000604b376", "5eb87d35ffd86e000604b37a", "5eb87d36ffd86e000604b37b", "5eb87d42ffd86e000604b384" ], "id": "5e9e3032383ecb267a34e7c7" }, ... ], "totalDocs": 7, "offset": 0, "limit": 10, "totalPages": 1, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": false, "prevPage": null, "nextPage": null } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/landpads/v4/schema.md ================================================ # Landing Pad Schema ```json { "name": { "type": "String", "default": null }, "full_name": { "type": "String", "default": null }, "status": { "type": "String", "enum": [ "active", "inactive", "unknown", "retired", "lost", "under construction" ], "required": true }, "type": { "type": "String", "default": null }, "locality": { "type": "String", "default": null }, "region": { "type": "String", "default": null }, "latitude": { "type": "Number", "default": null }, "longitude": { "type": "Number", "default": null }, "landing_attempts": { "type": "Number", "default": 0 }, "landing_successes": { "type": "Number", "default": 0 }, "wikipedia": { "type": "String", "default": null }, "details": { "type": "String", "default": null }, "launches": [ { "type": "UUID" } ] } ``` ================================================ FILE: docs/launches/v4/all.md ================================================ # Get all launches **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launches` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" }, ... ] ``` ================================================ FILE: docs/launches/v4/latest.md ================================================ # Get latest launch **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launches/latest` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ``` ================================================ FILE: docs/launches/v4/next.md ================================================ # Get next launch **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launches/next` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ``` ================================================ FILE: docs/launches/v4/one.md ================================================ # Get one launch **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launches/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the launch **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/launches/v4/past.md ================================================ # Get all past launches **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launches/past` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ... ] ``` ================================================ FILE: docs/launches/v4/query.md ================================================ # Query launches **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/launches/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "fairings": { "reused": false, "recovery_attempt": true, "recovered": false, "ships": ["5ea6ed2e080df4000697c908"] }, "links": { "patch": { "small": "https://images2.imgbox.com/02/51/7NLaBm8c_o.png", "large": "https://images2.imgbox.com/69/f5/04lBXd2F_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/73ttkd/koreasat_5a_launch_campaign_thread/", "launch": "https://www.reddit.com/r/spacex/comments/79iuvb/rspacex_koreasat_5a_official_launch_discussion/", "media": "https://www.reddit.com/r/spacex/comments/79lmdu/rspacex_koreasat5a_media_thread_videos_images/", "recovery": null }, "flickr": { "small": [], "original": [ "https://farm5.staticflickr.com/4477/38056454431_a5f40f9fd7_o.jpg", "https://farm5.staticflickr.com/4455/26280153979_b8016a829f_o.jpg", "https://farm5.staticflickr.com/4459/38056455051_79ef2b949a_o.jpg", "https://farm5.staticflickr.com/4466/26280153539_ecbc2b3fa9_o.jpg", "https://farm5.staticflickr.com/4482/26280154209_bf08d76361_o.jpg", "https://farm5.staticflickr.com/4493/38056455211_a4565a9cee_o.jpg" ] }, "presskit": "http://www.spacex.com/sites/spacex/files/koreasat5apresskit.pdf", "webcast": "https://www.youtube.com/watch?v=RUjH14vhLxA", "youtube_id": "RUjH14vhLxA", "article": "https://spaceflightnow.com/2017/10/30/spacex-launches-and-lands-third-rocket-in-three-weeks/", "wikipedia": "https://en.wikipedia.org/wiki/Koreasat_5A" }, "static_fire_date_utc": "2017-10-26T16:00:00.000Z", "static_fire_date_unix": 1509033600, "tdb": false, "net": false, "window": 8640, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "KoreaSat 5A is a Ku-band satellite capable of providing communication services from East Africa and Central Asia to southern India, Southeast Asia, the Philippines, Guam, Korea, and Japan. The satellite will be placed in GEO at 113° East Longitude, and will provide services ranging from broadband internet to broadcasting services and maritime communications.", "crew": [], "ships": [ "5ea6ed2f080df4000697c90d", "5ea6ed2e080df4000697c908", "5ea6ed30080df4000697c913" ], "capsules": [], "payloads": ["5eb0e4c5b6c3bb0006eeb217"], "launchpad": "5e9e4502f509094188566f88", "auto_update": true, "flight_number": 50, "name": "KoreaSat 5A", "date_utc": "2017-10-30T19:34:00.000Z", "date_unix": 1509392040, "date_local": "2017-10-30T15:34:00-04:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a4f359185cc03b2651", "flight": 1, "gridfins": true, "legs": true, "reused": false, "landing_attempt": true, "landing_success": true, "landing_type": "ASDS", "landpad": "5e9e3032383ecb6bb234e7ca" } ], "id": "5eb87d0dffd86e000604b35b" } ], "totalDocs": 109, "limit": 10, "totalPages": 11, "page": 5, "pagingCounter": 41, "hasPrevPage": true, "hasNextPage": true, "prevPage": 4, "nextPage": 6 } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/launches/v4/schema.md ================================================ # Launch Schema ```json { "flight_number": { "type": "Number", "required": true }, "name": { "type": "String", "unique": true, "required": true }, "date_utc": { "type": "String", "required": true }, "date_unix": { "type": "Number", "required": true }, "date_local": { "type": "String", "required": true }, "date_precision": { "type": "String", "required": true, "enum": [ "half", "quarter", "year", "month", "day", "hour" ] }, "static_fire_date_utc": { "type": "String", "default": null }, "static_fire_date_unix": { "type": "Number", "default": null }, "tdb": { "type": "Boolean", "default": false }, "net": { "type": "Boolean", "default": false }, "window": { "type": "Number", "default": null }, "rocket": { "type": "UUID", "default": null }, "success": { "type": "Boolean", "default": null }, "failures": [ { "time": { "type": "Number", }, "altitude": { "type": "Number", }, "reason": { "type": "String", }, }, ], "upcoming": { "type": "Boolean", "required": true }, "details": { "type": "String", "default": null }, "fairings": { "reused": { "type": "Boolean", "default": null }, "recovery_attempt": { "type": "Boolean", "default": null }, "recovered": { "type": "Boolean", "default": null }, "ships": [ "UUID" ] }, "crew": [ "UUID" ], "ships": [ "UUID" ], "capsules": [ "UUID" ], "payloads": [ "UUID" ], "launchpad": { "type": "UUID", "default": null }, "cores": [ { "core": { "type": "UUID", "default": null }, "flight": { "type": "Number", "default": null }, "gridfins": { "type": "Boolean", "default": null }, "legs": { "type": "Boolean", "default": null }, "reused": { "type": "Boolean", "default": null }, "landing_attempt": { "type": "Boolean", "default": null }, "landing_success": { "type": "Boolean", "default": null }, "landing_type": { "type": "String", "default": null }, "landpad": { "type": "UUID", "default": null } } ], "links": { "patch": { "small": { "type": "String", "default": null }, "large": { "type": "String", "default": null } }, "reddit": { "campaign": { "type": "String", "default": null }, "launch": { "type": "String", "default": null }, "media": { "type": "String", "default": null }, "recovery": { "type": "String", "default": null } }, "flickr": { "small": [ "String" ], "original": [ "String" ] }, "presskit": { "type": "String", "default": null }, "webcast": { "type": "String", "default": null }, "youtube_id": { "type": "String", "default": null }, "article": { "type": "String", "default": null }, "wikipedia": { "type": "String", "default": null } }, "auto_update": { "type": "Boolean", "default": true } } ``` ================================================ FILE: docs/launches/v4/upcoming.md ================================================ # Get all upcoming launches **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launches/upcoming` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ... ] ``` ================================================ FILE: docs/launches/v5/README.md ================================================ ## Changes from v4 -> v5 * Crew is now an array of objects, to allow for more data on an individual launch for a crew member ### Old Format ```json { "crew": [ "1234567890", "123456789", "123456789", ] } ``` ### New Format ```json { "crew": [ "1234567890", "123456789", "123456789", ] } ``` ================================================ FILE: docs/launches/v5/all.md ================================================ # Get all launches **Method** : `GET` **URL** : `https://api.spacexdata.com/v5/launches` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" }, ... ] ``` ================================================ FILE: docs/launches/v5/latest.md ================================================ # Get latest launch **Method** : `GET` **URL** : `https://api.spacexdata.com/v5/launches/latest` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ``` ================================================ FILE: docs/launches/v5/next.md ================================================ # Get next launch **Method** : `GET` **URL** : `https://api.spacexdata.com/v5/launches/next` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ``` ================================================ FILE: docs/launches/v5/one.md ================================================ # Get one launch **Method** : `GET` **URL** : `https://api.spacexdata.com/v5/launches/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the launch **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/launches/v5/past.md ================================================ # Get all past launches **Method** : `GET` **URL** : `https://api.spacexdata.com/v5/launches/past` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ... ] ``` ================================================ FILE: docs/launches/v5/query.md ================================================ # Query launches **Method** : `POST` **URL** : `https://api.spacexdata.com/v5/launches/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "fairings": { "reused": false, "recovery_attempt": true, "recovered": false, "ships": ["5ea6ed2e080df4000697c908"] }, "links": { "patch": { "small": "https://images2.imgbox.com/02/51/7NLaBm8c_o.png", "large": "https://images2.imgbox.com/69/f5/04lBXd2F_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/73ttkd/koreasat_5a_launch_campaign_thread/", "launch": "https://www.reddit.com/r/spacex/comments/79iuvb/rspacex_koreasat_5a_official_launch_discussion/", "media": "https://www.reddit.com/r/spacex/comments/79lmdu/rspacex_koreasat5a_media_thread_videos_images/", "recovery": null }, "flickr": { "small": [], "original": [ "https://farm5.staticflickr.com/4477/38056454431_a5f40f9fd7_o.jpg", "https://farm5.staticflickr.com/4455/26280153979_b8016a829f_o.jpg", "https://farm5.staticflickr.com/4459/38056455051_79ef2b949a_o.jpg", "https://farm5.staticflickr.com/4466/26280153539_ecbc2b3fa9_o.jpg", "https://farm5.staticflickr.com/4482/26280154209_bf08d76361_o.jpg", "https://farm5.staticflickr.com/4493/38056455211_a4565a9cee_o.jpg" ] }, "presskit": "http://www.spacex.com/sites/spacex/files/koreasat5apresskit.pdf", "webcast": "https://www.youtube.com/watch?v=RUjH14vhLxA", "youtube_id": "RUjH14vhLxA", "article": "https://spaceflightnow.com/2017/10/30/spacex-launches-and-lands-third-rocket-in-three-weeks/", "wikipedia": "https://en.wikipedia.org/wiki/Koreasat_5A" }, "static_fire_date_utc": "2017-10-26T16:00:00.000Z", "static_fire_date_unix": 1509033600, "tdb": false, "net": false, "window": 8640, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "KoreaSat 5A is a Ku-band satellite capable of providing communication services from East Africa and Central Asia to southern India, Southeast Asia, the Philippines, Guam, Korea, and Japan. The satellite will be placed in GEO at 113° East Longitude, and will provide services ranging from broadband internet to broadcasting services and maritime communications.", "crew": [], "ships": [ "5ea6ed2f080df4000697c90d", "5ea6ed2e080df4000697c908", "5ea6ed30080df4000697c913" ], "capsules": [], "payloads": ["5eb0e4c5b6c3bb0006eeb217"], "launchpad": "5e9e4502f509094188566f88", "auto_update": true, "flight_number": 50, "name": "KoreaSat 5A", "date_utc": "2017-10-30T19:34:00.000Z", "date_unix": 1509392040, "date_local": "2017-10-30T15:34:00-04:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a4f359185cc03b2651", "flight": 1, "gridfins": true, "legs": true, "reused": false, "landing_attempt": true, "landing_success": true, "landing_type": "ASDS", "landpad": "5e9e3032383ecb6bb234e7ca" } ], "id": "5eb87d0dffd86e000604b35b" } ], "totalDocs": 109, "limit": 10, "totalPages": 11, "page": 5, "pagingCounter": 41, "hasPrevPage": true, "hasNextPage": true, "prevPage": 4, "nextPage": 6 } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/launches/v5/schema.md ================================================ # Launch Schema ```json { "flight_number": { "type": "Number", "required": true }, "name": { "type": "String", "unique": true, "required": true }, "date_utc": { "type": "String", "required": true }, "date_unix": { "type": "Number", "required": true }, "date_local": { "type": "String", "required": true }, "date_precision": { "type": "String", "required": true, "enum": [ "half", "quarter", "year", "month", "day", "hour" ] }, "static_fire_date_utc": { "type": "String", "default": null }, "static_fire_date_unix": { "type": "Number", "default": null }, "tdb": { "type": "Boolean", "default": false }, "net": { "type": "Boolean", "default": false }, "window": { "type": "Number", "default": null }, "rocket": { "type": "UUID", "default": null }, "success": { "type": "Boolean", "default": null }, "failures": [ { "time": { "type": "Number", }, "altitude": { "type": "Number", }, "reason": { "type": "String", }, }, ], "upcoming": { "type": "Boolean", "required": true }, "details": { "type": "String", "default": null }, "fairings": { "reused": { "type": "Boolean", "default": null }, "recovery_attempt": { "type": "Boolean", "default": null }, "recovered": { "type": "Boolean", "default": null }, "ships": [ "UUID" ] }, "crew": [ { "crew": { "type": "UUID", "default": null }, "role": { "type": "String", "default": null }, } ], "ships": [ "UUID" ], "capsules": [ "UUID" ], "payloads": [ "UUID" ], "launchpad": { "type": "UUID", "default": null }, "cores": [ { "core": { "type": "UUID", "default": null }, "flight": { "type": "Number", "default": null }, "gridfins": { "type": "Boolean", "default": null }, "legs": { "type": "Boolean", "default": null }, "reused": { "type": "Boolean", "default": null }, "landing_attempt": { "type": "Boolean", "default": null }, "landing_success": { "type": "Boolean", "default": null }, "landing_type": { "type": "String", "default": null }, "landpad": { "type": "UUID", "default": null } } ], "links": { "patch": { "small": { "type": "String", "default": null }, "large": { "type": "String", "default": null } }, "reddit": { "campaign": { "type": "String", "default": null }, "launch": { "type": "String", "default": null }, "media": { "type": "String", "default": null }, "recovery": { "type": "String", "default": null } }, "flickr": { "small": [ "String" ], "original": [ "String" ] }, "presskit": { "type": "String", "default": null }, "webcast": { "type": "String", "default": null }, "youtube_id": { "type": "String", "default": null }, "article": { "type": "String", "default": null }, "wikipedia": { "type": "String", "default": null } }, "auto_update": { "type": "Boolean", "default": true } } ``` ================================================ FILE: docs/launches/v5/upcoming.md ================================================ # Get all upcoming launches **Method** : `GET` **URL** : `https://api.spacexdata.com/v5/launches/upcoming` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "fairings": null, "links": { "patch": { "small": "https://images2.imgbox.com/53/22/dh0XSLXO_o.png", "large": "https://images2.imgbox.com/15/2b/NAcsTEB6_o.png" }, "reddit": { "campaign": "https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread", "launch": "https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/", "media": "https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/", "recovery": null }, "flickr": { "small": [], "original": [ "https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg", "https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg", "https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg", "https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg", "https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg" ] }, "presskit": "https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf", "webcast": "https://youtu.be/1MkcWK2PnsU", "youtube_id": "1MkcWK2PnsU", "article": "https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/", "wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-20" }, "static_fire_date_utc": "2020-03-01T10:20:00.000Z", "static_fire_date_unix": 1583058000, "tdb": false, "net": false, "window": 0, "rocket": "5e9d0d95eda69973a809d1ec", "success": true, "failures": [], "details": "SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.", "crew": [], "ships": [], "capsules": [ "5e9e2c5cf359185d753b266f" ], "payloads": [ "5eb0e4d0b6c3bb0006eeb253" ], "launchpad": "5e9e4501f509094ba4566f84", "auto_update": true, "flight_number": 91, "name": "CRS-20", "date_utc": "2020-03-07T04:50:31.000Z", "date_unix": 1583556631, "date_local": "2020-03-06T23:50:31-05:00", "date_precision": "hour", "upcoming": false, "cores": [ { "core": "5e9e28a7f359187afd3b2662", "flight": 2, "gridfins": true, "legs": true, "reused": true, "landing_attempt": true, "landing_success": true, "landing_type": "RTLS", "landpad": "5e9e3032383ecb267a34e7c7" } ], "id": "5eb87d42ffd86e000604b384" } ... ] ``` ================================================ FILE: docs/launchpads/v4/all.md ================================================ # Get all launchpads **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launchpads` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "name": "VAFB SLC 4E", "full_name": "Vandenberg Air Force Base Space Launch Complex 4E", "locality": "Vandenberg Air Force Base", "region": "California", "timezone": "America/Los_Angeles", "latitude": 34.632093, "longitude": -120.610829, "launch_attempts": 15, "launch_successes": 15, "rockets": [ "5e9d0d95eda69973a809d1ec" ], "launches": [ "5eb87ce1ffd86e000604b334", "5eb87cf0ffd86e000604b343", "5eb87cfdffd86e000604b34c", "5eb87d05ffd86e000604b354", "5eb87d08ffd86e000604b357", "5eb87d0affd86e000604b359", "5eb87d0fffd86e000604b35d", "5eb87d14ffd86e000604b361", "5eb87d16ffd86e000604b363", "5eb87d1affd86e000604b367", "5eb87d1fffd86e000604b36b", "5eb87d23ffd86e000604b36e", "5eb87d25ffd86e000604b370", "5eb87d28ffd86e000604b373", "5eb87d31ffd86e000604b379" ], "status": "active", "id": "5e9e4502f509092b78566f87" }, ... ] ``` ================================================ FILE: docs/launchpads/v4/one.md ================================================ # Get one launchpad **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/launchpads/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the launchpad **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "name": "VAFB SLC 4E", "full_name": "Vandenberg Air Force Base Space Launch Complex 4E", "locality": "Vandenberg Air Force Base", "region": "California", "timezone": "America/Los_Angeles", "latitude": 34.632093, "longitude": -120.610829, "launch_attempts": 15, "launch_successes": 15, "rockets": [ "5e9d0d95eda69973a809d1ec" ], "launches": [ "5eb87ce1ffd86e000604b334", "5eb87cf0ffd86e000604b343", "5eb87cfdffd86e000604b34c", "5eb87d05ffd86e000604b354", "5eb87d08ffd86e000604b357", "5eb87d0affd86e000604b359", "5eb87d0fffd86e000604b35d", "5eb87d14ffd86e000604b361", "5eb87d16ffd86e000604b363", "5eb87d1affd86e000604b367", "5eb87d1fffd86e000604b36b", "5eb87d23ffd86e000604b36e", "5eb87d25ffd86e000604b370", "5eb87d28ffd86e000604b373", "5eb87d31ffd86e000604b379" ], "status": "active", "id": "5e9e4502f509092b78566f87" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/launchpads/v4/query.md ================================================ # Query launchpads **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/launchpads/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "name": "VAFB SLC 4E", "full_name": "Vandenberg Air Force Base Space Launch Complex 4E", "locality": "Vandenberg Air Force Base", "region": "California", "timezone": "America/Los_Angeles", "latitude": 34.632093, "longitude": -120.610829, "launch_attempts": 15, "launch_successes": 15, "rockets": [ "5e9d0d95eda69973a809d1ec" ], "launches": [ "5eb87ce1ffd86e000604b334", "5eb87cf0ffd86e000604b343", "5eb87cfdffd86e000604b34c", "5eb87d05ffd86e000604b354", "5eb87d08ffd86e000604b357", "5eb87d0affd86e000604b359", "5eb87d0fffd86e000604b35d", "5eb87d14ffd86e000604b361", "5eb87d16ffd86e000604b363", "5eb87d1affd86e000604b367", "5eb87d1fffd86e000604b36b", "5eb87d23ffd86e000604b36e", "5eb87d25ffd86e000604b370", "5eb87d28ffd86e000604b373", "5eb87d31ffd86e000604b379" ], "status": "active", "id": "5e9e4502f509092b78566f87" }, ... ], "totalDocs": 6, "offset": 0, "limit": 10, "totalPages": 1, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": false, "prevPage": null, "nextPage": null } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/launchpads/v4/schema.md ================================================ # Launchpad Schema ```json { "name": { "type": "String", "default": null }, "full_name": { "type": "String", "default": null }, "status": { "type": "String", "enum": [ "active", "inactive", "unknown", "retired", "lost", "under construction" ], "required": true }, "locality": { "type": "String", "default": null }, "region": { "type": "String", "default": null }, "timezone": { "type": "String", "default": null }, "latitude": { "type": "Number", "default": null }, "longitude": { "type": "Number", "default": null }, "launch_attempts": { "type": "Number", "default": 0 }, "launch_successes": { "type": "Number", "default": 0 }, "rockets": [ "UUID" ], "launches": [ "UUID" ] } ``` ================================================ FILE: docs/payloads/v4/all.md ================================================ # Get all payloads **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/payloads` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "dragon": { "capsule": null, "mass_returned_kg": null, "mass_returned_lbs": null, "flight_time_sec": null, "manifest": null, "water_landing": null, "land_landing": null }, "name": "Tintin A & B", "type": "Satellite", "reused": false, "launch": "5eb87d14ffd86e000604b361", "customers": [ "SpaceX" ], "norad_ids": [ 43216, 43217 ], "nationalities": [ "United States" ], "manufacturers": [ "SpaceX" ], "mass_kg": 800, "mass_lbs": 1763.7, "orbit": "SSO", "reference_system": "geocentric", "regime": "low-earth", "longitude": null, "semi_major_axis_km": 6737.42, "eccentricity": 0.0012995, "periapsis_km": 350.53, "apoapsis_km": 368.04, "inclination_deg": 97.4444, "period_min": 91.727, "lifespan_years": 1, "epoch": "2020-06-13T13:46:31.000Z", "mean_motion": 15.69864906, "raan": 176.6734, "arg_of_pericenter": 174.2326, "mean_anomaly": 185.9087, "id": "5eb0e4c6b6c3bb0006eeb21e" }, ... ] ``` ================================================ FILE: docs/payloads/v4/one.md ================================================ # Get one payload **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/payloads/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the payload **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "dragon": { "capsule": null, "mass_returned_kg": null, "mass_returned_lbs": null, "flight_time_sec": null, "manifest": null, "water_landing": null, "land_landing": null }, "name": "Tintin A & B", "type": "Satellite", "reused": false, "launch": "5eb87d14ffd86e000604b361", "customers": [ "SpaceX" ], "norad_ids": [ 43216, 43217 ], "nationalities": [ "United States" ], "manufacturers": [ "SpaceX" ], "mass_kg": 800, "mass_lbs": 1763.7, "orbit": "SSO", "reference_system": "geocentric", "regime": "low-earth", "longitude": null, "semi_major_axis_km": 6737.42, "eccentricity": 0.0012995, "periapsis_km": 350.53, "apoapsis_km": 368.04, "inclination_deg": 97.4444, "period_min": 91.727, "lifespan_years": 1, "epoch": "2020-06-13T13:46:31.000Z", "mean_motion": 15.69864906, "raan": 176.6734, "arg_of_pericenter": 174.2326, "mean_anomaly": 185.9087, "id": "5eb0e4c6b6c3bb0006eeb21e" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/payloads/v4/query.md ================================================ # Query payloads **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/payloads/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "dragon": { "capsule": null, "mass_returned_kg": null, "mass_returned_lbs": null, "flight_time_sec": null, "manifest": null, "water_landing": null, "land_landing": null }, "name": "Tintin A & B", "type": "Satellite", "reused": false, "launch": "5eb87d14ffd86e000604b361", "customers": [ "SpaceX" ], "norad_ids": [ 43216, 43217 ], "nationalities": [ "United States" ], "manufacturers": [ "SpaceX" ], "mass_kg": 800, "mass_lbs": 1763.7, "orbit": "SSO", "reference_system": "geocentric", "regime": "low-earth", "longitude": null, "semi_major_axis_km": 6737.42, "eccentricity": 0.0012995, "periapsis_km": 350.53, "apoapsis_km": 368.04, "inclination_deg": 97.4444, "period_min": 91.727, "lifespan_years": 1, "epoch": "2020-06-13T13:46:31.000Z", "mean_motion": 15.69864906, "raan": 176.6734, "arg_of_pericenter": 174.2326, "mean_anomaly": 185.9087, "id": "5eb0e4c6b6c3bb0006eeb21e" } ... ], "totalDocs": 136, "offset": 0, "limit": 10, "totalPages": 14, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": true, "prevPage": null, "nextPage": 2 } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/payloads/v4/schema.md ================================================ # Payload Schema ```json { "name": { "type": "String", "default": null, "unique": true }, "type": { "type": "String", "default": null }, "reused": { "type": "Boolean", "default": false }, "launch": { "type": "UUID", "default": null }, "customers": [ "String" ], "norad_ids": [ "Number" ], "nationalities": [ "String" ], "manufacturers": [ "String" ], "mass_kg": { "type": "Number", "default": null }, "mass_lbs": { "type": "Number", "default": null }, "orbit": { "type": "String", "default": null }, "reference_system": { "type": "String", "default": null }, "regime": { "type": "String", "default": null }, "longitude": { "type": "Number", "default": null }, "semi_major_axis_km": { "type": "Number", "default": null }, "eccentricity": { "type": "Number", "default": null }, "periapsis_km": { "type": "Number", "default": null }, "apoapsis_km": { "type": "Number", "default": null }, "inclination_deg": { "type": "Number", "default": null }, "period_min": { "type": "Number", "default": null }, "lifespan_years": { "type": "Number", "default": null }, "epoch": { "type": "String", "default": null }, "mean_motion": { "type": "Number", "default": null }, "raan": { "type": "Number", "default": null }, "arg_of_pericenter": { "type": "Number", "default": null }, "mean_anomaly": { "type": "Number", "default": null }, "dragon": { "capsule": { "type": "UUID", "default": null }, "mass_returned_kg": { "type": "Number", "default": null }, "mass_returned_lbs": { "type": "Number", "default": null }, "flight_time_sec": { "type": "Number", "default": null }, "manifest": { "type": "String", "default": null }, "water_landing": { "type": "Boolean", "default": null }, "land_landing": { "type": "Boolean", "default": null } } } ``` ================================================ FILE: docs/queries.md ================================================ # Query + Pagination Guide All `/query` routes support pagination parameters via [mongoose-paginate](https://github.com/aravindnc/mongoose-paginate-v2). The default body for `/query` routes is: ```json { "query": {}, "options": {}, } ``` `query` accepts any valid MongoDB find() query, documented [here](https://docs.mongodb.com/manual/tutorial/query-documents/) `options` accepts any of the options documented [here](https://github.com/aravindnc/mongoose-paginate-v2#modelpaginatequery-options-callback), but here are some of the most common: - `select` { Object | String } - Fields to return (by default returns all fields). [Documentation](http://mongoosejs.com/docs/api.html#query_Query-select) - `sort` { Object | String } - Sort order. [Documentation](http://mongoosejs.com/docs/api.html#query_Query-sort) - `offset` { Number } - Use `offset` or `page` to set skip position - `page` { Number } - `limit` { Number } - `pagination` { Boolean } - If set to false, it will return all docs without adding limit condition. (Default: True) - `populate` {Array | Object | String} - Paths which should be populated with other documents. [Documentation](https://mongoosejs.com/docs/api.html#query_Query-populate) This is the default return structure for paginated results: ```json { "docs": [], "totalDocs": 0, "offset": 0, "limit": 10, "totalPages": 1, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": false, "prevPage": null, "nextPage": null } ``` By default, UUID's are used to reference documents in another collection. For example, the [launches](launches/query.md) endpoint has an array of UUID's named `payloads` that references a payload in the [payloads](payloads/query.md) endpoint. ```json { "payloads": [ "5eb0e4c6b6c3bb0006eeb21e" ] } ``` This allows us to populate or replace the UUID with the payload that it references. In this example, to populate `payloads` with the corresponding document, we would send a `POST` request to `https://api.spacexdata.com/v4/launches/query` with the following body: ```json { "query": {}, "options": { "populate": [ "payloads" ] }, } ``` Which returns the linked payload object in place of the UUID: ```json { ... "payloads": [ { "dragon": { "capsule": null, "mass_returned_kg": null, "mass_returned_lbs": null, "flight_time_sec": null, "manifest": null, "water_landing": null, "land_landing": null }, "name": "Tintin A & B", "type": "Satellite", "reused": false, "launch": "5eb87d14ffd86e000604b361", "customers": [ "SpaceX" ], "norad_ids": [ 43216, 43217 ], "nationalities": [ "United States" ], "manufacturers": [ "SpaceX" ], "mass_kg": 800, "mass_lbs": 1763.7, "orbit": "SSO", "reference_system": "geocentric", "regime": "low-earth", "longitude": null, "semi_major_axis_km": 6737.42, "eccentricity": 0.0012995, "periapsis_km": 350.53, "apoapsis_km": 368.04, "inclination_deg": 97.4444, "period_min": 91.727, "lifespan_years": 1, "epoch": "2020-06-13T13:46:31.000Z", "mean_motion": 15.69864906, "raan": 176.6734, "arg_of_pericenter": 174.2326, "mean_anomaly": 185.9087, "id": "5eb0e4c6b6c3bb0006eeb21e" } ] ... } ``` Populate also allows you to select specific fields to return. For example, if you were only interested in the payload `name`, you could use the following: ```json { "options": { "populate": [ { "path": "payloads", "select": { "name": 1 } } ] } } ``` Which would return: ```json { "payloads": [ { "name": "Tintin A & B", "id": "5eb0e4c6b6c3bb0006eeb21e" } ] } ``` Populate can also be nested inside another populate to recursively fill fields. For example, you could populate the `payloads` array, and also populate the `launch` property inside each payload: ```json { "options": { "populate": [ { "path":"payloads", "populate": [ { "path":"launch" } ] } ] } } ``` ## Examples ### Query between 2 dates Dates need to be ISO 8601 friendly for these operators to work properly ```json { "query": { "date_utc": { "$gte": "2017-06-22T00:00:00.000Z", "$lte": "2017-06-25T00:00:00.000Z" } } } ``` ### Full text search This will search all text indexes in a collection. All string fields get indexed See the mongo [reference](https://docs.mongodb.com/manual/reference/operator/query/text/) for more details on additional operators. ```json { "query": { "$text": { "$search": "crs" } } } ``` ### Next Upcoming Launch ```json { "query":{ "upcoming":true }, "options":{ "limit":1, "sort":{ "flight_number":"asc" } } } ``` ### Complex Query ```json { "query": { "date_utc": { "$gte": "2017-06-22T00:00:00.000Z", "$lte": "2017-06-25T00:00:00.000Z" }, "$or": [ { "flight_number": { "$gt": 30 } }, { "tbd": true } ], "date_precision": { "$in": [ "month", "day" ] } }, "options": { "sort": { "flight_number": "asc" }, "limit": 50 } } ``` ================================================ FILE: docs/roadster/v4/get.md ================================================ # Get roadster info **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/roadster` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json { "flickr_images": [ "https://farm5.staticflickr.com/4615/40143096241_11128929df_b.jpg", "https://farm5.staticflickr.com/4702/40110298232_91b32d0cc0_b.jpg", "https://farm5.staticflickr.com/4676/40110297852_5e794b3258_b.jpg", "https://farm5.staticflickr.com/4745/40110304192_6e3e9a7a1b_b.jpg" ], "name": "Elon Musk's Tesla Roadster", "launch_date_utc": "2018-02-06T20:45:00.000Z", "launch_date_unix": 1517949900, "launch_mass_kg": 1350, "launch_mass_lbs": 2976, "norad_id": 43205, "epoch_jd": 2459014.345891204, "orbit_type": "heliocentric", "apoapsis_au": 1.663950009802517, "periapsis_au": 0.9859657216725529, "semi_major_axis_au": 196.2991348009594, "eccentricity": 0.2558512635239784, "inclination": 1.077499248052439, "longitude": 317.0839961949045, "periapsis_arg": 177.5240278992875, "period_days": 557.059427465354, "speed_kph": 72209.97792, "speed_mph": 44869.18619012833, "earth_distance_km": 220606726.83228922, "earth_distance_mi": 137078622.45850638, "mars_distance_km": 89348334.47067611, "mars_distance_mi": 55518463.93837848, "wikipedia": "https://en.wikipedia.org/wiki/Elon_Musk%27s_Tesla_Roadster", "video": "https://youtu.be/wbSwFU6tY1c", "details": "Elon Musk's Tesla Roadster is an electric sports car that served as the dummy payload for the February 2018 Falcon Heavy test flight and is now an artificial satellite of the Sun. Starman, a mannequin dressed in a spacesuit, occupies the driver's seat. The car and rocket are products of Tesla and SpaceX. This 2008-model Roadster was previously used by Musk for commuting, and is the only consumer car sent into space.", "id": "5eb75f0842fea42237d7f3f4" } ``` ================================================ FILE: docs/roadster/v4/query.md ================================================ # Query roadster **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/roadster/query` **Auth required** : `False` **Body** : **NOTE:** Unlike other `/query` endpoints, this does not provide pagination, and only exposes the `select` ability in `options` to allow you to hide/show specific fields in the response. For more info on how select works, see the [mongoose](https://mongoosejs.com/docs/api.html#query_Query-select) docs. ```json { "query": {}, "options": { "select": { "norad_id": 1, } } } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "flickr_images": [ "https://farm5.staticflickr.com/4615/40143096241_11128929df_b.jpg", "https://farm5.staticflickr.com/4702/40110298232_91b32d0cc0_b.jpg", "https://farm5.staticflickr.com/4676/40110297852_5e794b3258_b.jpg", "https://farm5.staticflickr.com/4745/40110304192_6e3e9a7a1b_b.jpg" ], "name": "Elon Musk's Tesla Roadster", "launch_date_utc": "2018-02-06T20:45:00.000Z", "launch_date_unix": 1517949900, "launch_mass_kg": 1350, "launch_mass_lbs": 2976, "norad_id": 43205, "epoch_jd": 2459014.345891204, "orbit_type": "heliocentric", "apoapsis_au": 1.663950009802517, "periapsis_au": 0.9859657216725529, "semi_major_axis_au": 196.2991348009594, "eccentricity": 0.2558512635239784, "inclination": 1.077499248052439, "longitude": 317.0839961949045, "periapsis_arg": 177.5240278992875, "period_days": 557.059427465354, "speed_kph": 72209.97792, "speed_mph": 44869.18619012833, "earth_distance_km": 220606726.83228922, "earth_distance_mi": 137078622.45850638, "mars_distance_km": 89348334.47067611, "mars_distance_mi": 55518463.93837848, "wikipedia": "https://en.wikipedia.org/wiki/Elon_Musk%27s_Tesla_Roadster", "video": "https://youtu.be/wbSwFU6tY1c", "details": "Elon Musk's Tesla Roadster is an electric sports car that served as the dummy payload for the February 2018 Falcon Heavy test flight and is now an artificial satellite of the Sun. Starman, a mannequin dressed in a spacesuit, occupies the driver's seat. The car and rocket are products of Tesla and SpaceX. This 2008-model Roadster was previously used by Musk for commuting, and is the only consumer car sent into space.", "id": "5eb75f0842fea42237d7f3f4" } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/roadster/v4/schema.md ================================================ # Roadster Schema ```json { "name": { "type": "String" }, "launch_date_utc": { "type": "String" }, "launch_date_unix": { "type": "Number" }, "launch_mass_kg": { "type": "Number" }, "launch_mass_lbs": { "type": "Number" }, "norad_id": { "type": "Number" }, "epoch_jd": { "type": "Number" }, "orbit_type": { "type": "String" }, "apoapsis_au": { "type": "Number" }, "periapsis_au": { "type": "Number" }, "semi_major_axis_au": { "type": "Number" }, "eccentricity": { "type": "Number" }, "inclination": { "type": "Number" }, "longitude": { "type": "Number" }, "periapsis_arg": { "type": "Number" }, "period_days": { "type": "Number" }, "speed_kph": { "type": "Number" }, "speed_mph": { "type": "Number" }, "earth_distance_km": { "type": "Number" }, "earth_distance_mi": { "type": "Number" }, "mars_distance_km": { "type": "Number" }, "mars_distance_mi": { "type": "Number" }, "flickr_images": [ "String" ], "wikipedia": { "type": "String" }, "video": { "type": "String" }, "details": { "type": "String" } } ``` ================================================ FILE: docs/rockets/v4/all.md ================================================ # Get all rockets **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/rockets` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "height": { "meters": 70, "feet": 229.6 }, "diameter": { "meters": 12.2, "feet": 39.9 }, "mass": { "kg": 1420788, "lb": 3125735 }, "first_stage": { "thrust_sea_level": { "kN": 22819, "lbf": 5130000 }, "thrust_vacuum": { "kN": 24681, "lbf": 5548500 }, "reusable": true, "engines": 27, "fuel_amount_tons": 1155, "burn_time_sec": 162 }, "second_stage": { "thrust": { "kN": 934, "lbf": 210000 }, "payloads": { "composite_fairing": { "height": { "meters": 13.1, "feet": 43 }, "diameter": { "meters": 5.2, "feet": 17.1 } }, "option_1": "dragon" }, "reusable": false, "engines": 1, "fuel_amount_tons": 90, "burn_time_sec": 397 }, "engines": { "isp": { "sea_level": 288, "vacuum": 312 }, "thrust_sea_level": { "kN": 845, "lbf": 190000 }, "thrust_vacuum": { "kN": 914, "lbf": 205500 }, "number": 27, "type": "merlin", "version": "1D+", "layout": "octaweb", "engine_loss_max": 6, "propellant_1": "liquid oxygen", "propellant_2": "RP-1 kerosene", "thrust_to_weight": 180.1 }, "landing_legs": { "number": 12, "material": "carbon fiber" }, "payload_weights": [ { "id": "leo", "name": "Low Earth Orbit", "kg": 63800, "lb": 140660 }, { "id": "gto", "name": "Geosynchronous Transfer Orbit", "kg": 26700, "lb": 58860 }, { "id": "mars", "name": "Mars Orbit", "kg": 16800, "lb": 37040 }, { "id": "pluto", "name": "Pluto Orbit", "kg": 3500, "lb": 7720 } ], "flickr_images": [ "https://farm5.staticflickr.com/4599/38583829295_581f34dd84_b.jpg", "https://farm5.staticflickr.com/4645/38583830575_3f0f7215e6_b.jpg", "https://farm5.staticflickr.com/4696/40126460511_b15bf84c85_b.jpg", "https://farm5.staticflickr.com/4711/40126461411_aabc643fd8_b.jpg" ], "name": "Falcon Heavy", "type": "rocket", "active": true, "stages": 2, "boosters": 2, "cost_per_launch": 90000000, "success_rate_pct": 100, "first_flight": "2018-02-06", "country": "United States", "company": "SpaceX", "wikipedia": "https://en.wikipedia.org/wiki/Falcon_Heavy", "description": "With the ability to lift into orbit over 54 metric tons (119,000 lb)--a mass equivalent to a 737 jetliner loaded with passengers, crew, luggage and fuel--Falcon Heavy can lift more than twice the payload of the next closest operational vehicle, the Delta IV Heavy, at one-third the cost.", "id": "5e9d0d95eda69974db09d1ed" }, ... ] ``` ================================================ FILE: docs/rockets/v4/one.md ================================================ # Get one rocket **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/rockets/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the rocket **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "height": { "meters": 70, "feet": 229.6 }, "diameter": { "meters": 12.2, "feet": 39.9 }, "mass": { "kg": 1420788, "lb": 3125735 }, "first_stage": { "thrust_sea_level": { "kN": 22819, "lbf": 5130000 }, "thrust_vacuum": { "kN": 24681, "lbf": 5548500 }, "reusable": true, "engines": 27, "fuel_amount_tons": 1155, "burn_time_sec": 162 }, "second_stage": { "thrust": { "kN": 934, "lbf": 210000 }, "payloads": { "composite_fairing": { "height": { "meters": 13.1, "feet": 43 }, "diameter": { "meters": 5.2, "feet": 17.1 } }, "option_1": "dragon" }, "reusable": false, "engines": 1, "fuel_amount_tons": 90, "burn_time_sec": 397 }, "engines": { "isp": { "sea_level": 288, "vacuum": 312 }, "thrust_sea_level": { "kN": 845, "lbf": 190000 }, "thrust_vacuum": { "kN": 914, "lbf": 205500 }, "number": 27, "type": "merlin", "version": "1D+", "layout": "octaweb", "engine_loss_max": 6, "propellant_1": "liquid oxygen", "propellant_2": "RP-1 kerosene", "thrust_to_weight": 180.1 }, "landing_legs": { "number": 12, "material": "carbon fiber" }, "payload_weights": [ { "id": "leo", "name": "Low Earth Orbit", "kg": 63800, "lb": 140660 }, { "id": "gto", "name": "Geosynchronous Transfer Orbit", "kg": 26700, "lb": 58860 }, { "id": "mars", "name": "Mars Orbit", "kg": 16800, "lb": 37040 }, { "id": "pluto", "name": "Pluto Orbit", "kg": 3500, "lb": 7720 } ], "flickr_images": [ "https://farm5.staticflickr.com/4599/38583829295_581f34dd84_b.jpg", "https://farm5.staticflickr.com/4645/38583830575_3f0f7215e6_b.jpg", "https://farm5.staticflickr.com/4696/40126460511_b15bf84c85_b.jpg", "https://farm5.staticflickr.com/4711/40126461411_aabc643fd8_b.jpg" ], "name": "Falcon Heavy", "type": "rocket", "active": true, "stages": 2, "boosters": 2, "cost_per_launch": 90000000, "success_rate_pct": 100, "first_flight": "2018-02-06", "country": "United States", "company": "SpaceX", "wikipedia": "https://en.wikipedia.org/wiki/Falcon_Heavy", "description": "With the ability to lift into orbit over 54 metric tons (119,000 lb)--a mass equivalent to a 737 jetliner loaded with passengers, crew, luggage and fuel--Falcon Heavy can lift more than twice the payload of the next closest operational vehicle, the Delta IV Heavy, at one-third the cost.", "id": "5e9d0d95eda69974db09d1ed" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/rockets/v4/query.md ================================================ # Query rockets **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/rockets/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "height": { "meters": 70, "feet": 229.6 }, "diameter": { "meters": 3.7, "feet": 12 }, "mass": { "kg": 549054, "lb": 1207920 }, "first_stage": { "thrust_sea_level": { "kN": 7607, "lbf": 1710000 }, "thrust_vacuum": { "kN": 8227, "lbf": 1849500 }, "reusable": true, "engines": 9, "fuel_amount_tons": 385, "burn_time_sec": 162 }, "second_stage": { "thrust": { "kN": 934, "lbf": 210000 }, "payloads": { "composite_fairing": { "height": { "meters": 13.1, "feet": 43 }, "diameter": { "meters": 5.2, "feet": 17.1 } }, "option_1": "dragon" }, "reusable": false, "engines": 1, "fuel_amount_tons": 90, "burn_time_sec": 397 }, "engines": { "isp": { "sea_level": 288, "vacuum": 312 }, "thrust_sea_level": { "kN": 845, "lbf": 190000 }, "thrust_vacuum": { "kN": 914, "lbf": 205500 }, "number": 9, "type": "merlin", "version": "1D+", "layout": "octaweb", "engine_loss_max": 2, "propellant_1": "liquid oxygen", "propellant_2": "RP-1 kerosene", "thrust_to_weight": 180.1 }, "landing_legs": { "number": 4, "material": "carbon fiber" }, "payload_weights": [ { "id": "leo", "name": "Low Earth Orbit", "kg": 22800, "lb": 50265 }, { "id": "gto", "name": "Geosynchronous Transfer Orbit", "kg": 8300, "lb": 18300 }, { "id": "mars", "name": "Mars Orbit", "kg": 4020, "lb": 8860 } ], "flickr_images": [ "https://farm1.staticflickr.com/929/28787338307_3453a11a77_b.jpg", "https://farm4.staticflickr.com/3955/32915197674_eee74d81bb_b.jpg", "https://farm1.staticflickr.com/293/32312415025_6841e30bf1_b.jpg", "https://farm1.staticflickr.com/623/23660653516_5b6cb301d1_b.jpg", "https://farm6.staticflickr.com/5518/31579784413_d853331601_b.jpg", "https://farm1.staticflickr.com/745/32394687645_a9c54a34ef_b.jpg" ], "name": "Falcon 9", "type": "rocket", "active": true, "stages": 2, "boosters": 0, "cost_per_launch": 50000000, "success_rate_pct": 97, "first_flight": "2010-06-04", "country": "United States", "company": "SpaceX", "wikipedia": "https://en.wikipedia.org/wiki/Falcon_9", "description": "Falcon 9 is a two-stage rocket designed and manufactured by SpaceX for the reliable and safe transport of satellites and the Dragon spacecraft into orbit.", "id": "5e9d0d95eda69973a809d1ec" }, ... ], "totalDocs": 4, "offset": 0, "limit": 10, "totalPages": 1, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": false, "prevPage": null, "nextPage": null } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/rockets/v4/schema.md ================================================ # Rocket Schema ```json { "name": { "type": "String" }, "type": { "type": "String" }, "active": { "type": "Boolean" }, "stages": { "type": "Number" }, "boosters": { "type": "Number" }, "cost_per_launch": { "type": "Number" }, "success_rate_pct": { "type": "Number" }, "first_flight": { "type": "String" }, "country": { "type": "String" }, "company": { "type": "String" }, "height": { "meters": { "type": "Number" }, "feet": { "type": "Number" } }, "diameter": { "meters": { "type": "Number" }, "feet": { "type": "Number" } }, "mass": { "kg": { "type": "Number" }, "lb": { "type": "Number" } }, "payload_weights": { "type": [ "Object" ] }, "first_stage": { "reusable": { "type": "Boolean" }, "engines": { "type": "Number" }, "fuel_amount_tons": { "type": "Number" }, "burn_time_sec": { "type": "Number" }, "thrust_sea_level": { "kN": { "type": "Number" }, "lbf": { "type": "Number" } }, "thrust_vacuum": { "kN": { "type": "Number" }, "lbf": { "type": "Number" } } }, "second_stage": { "reusable": { "type": "Boolean" }, "engines": { "type": "Number" }, "fuel_amount_tons": { "type": "Number" }, "burn_time_sec": { "type": "Number" }, "thrust": { "kN": { "type": "Number" }, "lbf": { "type": "Number" } }, "payloads": { "option_1": { "type": "String" }, "composite_fairing": { "height": { "meters": { "type": "Number" }, "feet": { "type": "Number" } }, "diameter": { "meters": { "type": "Number" }, "feet": { "type": "Number" } } } } }, "engines": { "number": { "type": "Number" }, "type": { "type": "String" }, "version": { "type": "String" }, "layout": { "type": "String" }, "isp": { "sea_level": { "type": "Number" }, "vacuum": { "type": "Number" } }, "engine_loss_max": { "type": "Number" }, "propellant_1": { "type": "String" }, "propellant_2": { "type": "String" }, "thrust_sea_level": { "kN": { "type": "Number" }, "lbf": { "type": "Number" } }, "thrust_vacuum": { "kN": { "type": "Number" }, "lbf": { "type": "Number" } }, "thrust_to_weight": { "type": "Number" } }, "landing_legs": { "number": { "type": "Number" }, "material": { "type": "Object" } }, "flickr_images": { "type": [ "String" ] }, "wikipedia": { "type": "String" }, "description": { "type": "String" } } ``` ================================================ FILE: docs/ships/v4/all.md ================================================ # Get all ships **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/ships` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "legacy_id": "GOPURSUIT", "model": null, "type": "Cargo", "roles": [ "Support Ship", "Fairing Recovery" ], "imo": 9458884, "mmsi": 367191410, "abs": 1201189, "class": 7174230, "mass_kg": 502999, "mass_lbs": 1108925, "year_built": 2007, "home_port": "Port Canaveral", "status": "", "speed_kn": null, "course_deg": null, "latitude": null, "longitude": null, "last_ais_update": null, "link": "https://www.marinetraffic.com/en/ais/details/ships/shipid:439594/mmsi:367191410/imo:9458884/vessel:GO_PURSUIT", "image": "https://i.imgur.com/5w1ZWre.jpg", "launches": [ "5eb87d18ffd86e000604b365", "5eb87d19ffd86e000604b366", "5eb87d1bffd86e000604b368", "5eb87d1effd86e000604b36a" ], "name": "GO Pursuit", "active": false, "id": "5ea6ed2e080df4000697c90a" }, ... ] ``` ================================================ FILE: docs/ships/v4/one.md ================================================ # Get one ship **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/ships/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the ship **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "legacy_id": "GOPURSUIT", "model": null, "type": "Cargo", "roles": [ "Support Ship", "Fairing Recovery" ], "imo": 9458884, "mmsi": 367191410, "abs": 1201189, "class": 7174230, "mass_kg": 502999, "mass_lbs": 1108925, "year_built": 2007, "home_port": "Port Canaveral", "status": "", "speed_kn": null, "course_deg": null, "latitude": null, "longitude": null, "last_ais_update": null, "link": "https://www.marinetraffic.com/en/ais/details/ships/shipid:439594/mmsi:367191410/imo:9458884/vessel:GO_PURSUIT", "image": "https://i.imgur.com/5w1ZWre.jpg", "launches": [ "5eb87d18ffd86e000604b365", "5eb87d19ffd86e000604b366", "5eb87d1bffd86e000604b368", "5eb87d1effd86e000604b36a" ], "name": "GO Pursuit", "active": false, "id": "5ea6ed2e080df4000697c90a" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/ships/v4/query.md ================================================ # Query ships **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/ships/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "legacy_id": "GOPURSUIT", "model": null, "type": "Cargo", "roles": [ "Support Ship", "Fairing Recovery" ], "imo": 9458884, "mmsi": 367191410, "abs": 1201189, "class": 7174230, "mass_kg": 502999, "mass_lbs": 1108925, "year_built": 2007, "home_port": "Port Canaveral", "status": "", "speed_kn": null, "course_deg": null, "latitude": null, "longitude": null, "last_ais_update": null, "link": "https://www.marinetraffic.com/en/ais/details/ships/shipid:439594/mmsi:367191410/imo:9458884/vessel:GO_PURSUIT", "image": "https://i.imgur.com/5w1ZWre.jpg", "launches": [ "5eb87d18ffd86e000604b365", "5eb87d19ffd86e000604b366", "5eb87d1bffd86e000604b368", "5eb87d1effd86e000604b36a" ], "name": "GO Pursuit", "active": false, "id": "5ea6ed2e080df4000697c90a" }, ... ], "totalDocs": 22, "offset": 0, "limit": 10, "totalPages": 3, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": true, "prevPage": null, "nextPage": 2 } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/ships/v4/schema.md ================================================ # Ship Schema ```json { "name": { "type": "String", "unique": true, "required": true }, "legacy_id": { "type": "String", "default": null }, "model": { "type": "String", "default": null }, "type": { "type": "String", "default": null }, "roles": [ "String" ], "active": { "type": "Boolean", "required": true }, "imo": { "type": "Number", "default": null }, "mmsi": { "type": "Number", "default": null }, "abs": { "type": "Number", "default": null }, "class": { "type": "Number", "default": null }, "mass_kg": { "type": "Number", "default": null }, "mass_lbs": { "type": "Number", "default": null }, "year_built": { "type": "Number", "default": null }, "home_port": { "type": "String", "default": null }, "status": { "type": "String", "default": null }, "speed_kn": { "type": "Number", "default": null }, "course_deg": { "type": "Number", "default": null }, "latitude": { "type": "Number", "default": null }, "longitude": { "type": "Number", "default": null }, "last_ais_update": { "type": "String", "default": null }, "link": { "type": "String", "default": null }, "image": { "type": "String", "default": null }, "launches": [ { "type": "UUID" } ] } ``` ================================================ FILE: docs/starlink/v4/all.md ================================================ # Get all Starlink satellites **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/starlink` **Auth required** : `False` ## Success Responses **Code** : `200 OK` ```json [ { "spaceTrack": { "CCSDS_OMM_VERS": "2.0", "COMMENT": "GENERATED VIA SPACE-TRACK.ORG API", "CREATION_DATE": "2020-06-19 21:46:09", "ORIGINATOR": "18 SPCS", "OBJECT_NAME": "STARLINK-1506", "OBJECT_ID": "2020-038T", "CENTER_NAME": "EARTH", "REF_FRAME": "TEME", "TIME_SYSTEM": "UTC", "MEAN_ELEMENT_THEORY": "SGP4", "EPOCH": "2020-06-19 20:00:01.000224", "MEAN_MOTION": 15.88829743, "ECCENTRICITY": 0.0087515, "INCLINATION": 53.002, "RA_OF_ASC_NODE": 266.3302, "ARG_OF_PERICENTER": 69.9474, "MEAN_ANOMALY": 221.4733, "EPHEMERIS_TYPE": 0, "CLASSIFICATION_TYPE": "U", "NORAD_CAT_ID": 45747, "ELEMENT_SET_NO": 999, "REV_AT_EPOCH": 212, "BSTAR": 0.01007, "MEAN_MOTION_DOT": 0.03503094, "MEAN_MOTION_DDOT": 0.01265, "SEMIMAJOR_AXIS": 6683.699, "PERIOD": 90.632, "APOAPSIS": 364.057, "PERIAPSIS": 247.072, "OBJECT_TYPE": "PAYLOAD", "RCS_SIZE": null, "COUNTRY_CODE": "US", "LAUNCH_DATE": "2020-06-13", "SITE": "AFETR", "DECAY_DATE": null, "DECAYED": 0, "FILE": 2768947, "GP_ID": 155985688, "TLE_LINE0": "0 STARLINK-1506", "TLE_LINE1": "1 45747U 20038T 20171.83334491 .03503094 12654-1 10068-1 0 9995", "TLE_LINE2": "2 45747 53.0017 266.3302 0087515 69.9474 221.4733 15.88829743 2124" }, "version": "v1.0", "launch": "5eb87d46ffd86e000604b389", "longitude": 165.93047730624068, "latitude": -52.91311434465077, "height_km": 446.61936740361125, "velocity_kms": 7.643507427834188, "id": "5eed7716096e590006985825" } ... ] ``` ================================================ FILE: docs/starlink/v4/one.md ================================================ # Get one Starlink satellite **Method** : `GET` **URL** : `https://api.spacexdata.com/v4/starlink/:id` **URL Parameters** : `id=[string]` where `id` is the ID of the Starlink sat **Auth required** : `False` ## Success Response **Code** : `200 OK` **Content example** : ```json { "spaceTrack": { "CCSDS_OMM_VERS": "2.0", "COMMENT": "GENERATED VIA SPACE-TRACK.ORG API", "CREATION_DATE": "2020-06-19 21:46:09", "ORIGINATOR": "18 SPCS", "OBJECT_NAME": "STARLINK-1506", "OBJECT_ID": "2020-038T", "CENTER_NAME": "EARTH", "REF_FRAME": "TEME", "TIME_SYSTEM": "UTC", "MEAN_ELEMENT_THEORY": "SGP4", "EPOCH": "2020-06-19 20:00:01.000224", "MEAN_MOTION": 15.88829743, "ECCENTRICITY": 0.0087515, "INCLINATION": 53.002, "RA_OF_ASC_NODE": 266.3302, "ARG_OF_PERICENTER": 69.9474, "MEAN_ANOMALY": 221.4733, "EPHEMERIS_TYPE": 0, "CLASSIFICATION_TYPE": "U", "NORAD_CAT_ID": 45747, "ELEMENT_SET_NO": 999, "REV_AT_EPOCH": 212, "BSTAR": 0.01007, "MEAN_MOTION_DOT": 0.03503094, "MEAN_MOTION_DDOT": 0.01265, "SEMIMAJOR_AXIS": 6683.699, "PERIOD": 90.632, "APOAPSIS": 364.057, "PERIAPSIS": 247.072, "OBJECT_TYPE": "PAYLOAD", "RCS_SIZE": null, "COUNTRY_CODE": "US", "LAUNCH_DATE": "2020-06-13", "SITE": "AFETR", "DECAY_DATE": null, "DECAYED": 0, "FILE": 2768947, "GP_ID": 155985688, "TLE_LINE0": "0 STARLINK-1506", "TLE_LINE1": "1 45747U 20038T 20171.83334491 .03503094 12654-1 10068-1 0 9995", "TLE_LINE2": "2 45747 53.0017 266.3302 0087515 69.9474 221.4733 15.88829743 2124" }, "version": "v1.0", "launch": "5eb87d46ffd86e000604b389", "longitude": 165.93047730624068, "latitude": -52.91311434465077, "height_km": 446.61936740361125, "velocity_kms": 7.643507427834188, "id": "5eed7716096e590006985825" } ``` ## Error Responses **Code** : `404 NOT FOUND` **Content** : `Not Found` ================================================ FILE: docs/starlink/v4/query.md ================================================ # Query Starlink satellites **Method** : `POST` **URL** : `https://api.spacexdata.com/v4/starlink/query` **Auth required** : `False` **Body** : See [query](../../queries.md) guide for more details on building queries and paginating results. ```json { "query": {}, "options": {} } ``` ## Success Response **Code** : `200 OK` **Content example** : ```json { "docs": [ { "spaceTrack": { "CCSDS_OMM_VERS": "2.0", "COMMENT": "GENERATED VIA SPACE-TRACK.ORG API", "CREATION_DATE": "2020-06-19 21:36:08", "ORIGINATOR": "18 SPCS", "OBJECT_NAME": "STARLINK-30", "OBJECT_ID": "2019-029K", "CENTER_NAME": "EARTH", "REF_FRAME": "TEME", "TIME_SYSTEM": "UTC", "MEAN_ELEMENT_THEORY": "SGP4", "EPOCH": "2020-06-19 20:00:01.000224", "MEAN_MOTION": 15.43862877, "ECCENTRICITY": 0.000125, "INCLINATION": 52.996, "RA_OF_ASC_NODE": 195.8544, "ARG_OF_PERICENTER": 108.6906, "MEAN_ANOMALY": 109.3199, "EPHEMERIS_TYPE": 0, "CLASSIFICATION_TYPE": "U", "NORAD_CAT_ID": 44244, "ELEMENT_SET_NO": 999, "REV_AT_EPOCH": 5947, "BSTAR": 0.00007, "MEAN_MOTION_DOT": 0.00002829, "MEAN_MOTION_DDOT": 0, "SEMIMAJOR_AXIS": 6812.858, "PERIOD": 93.272, "APOAPSIS": 435.574, "PERIAPSIS": 433.871, "OBJECT_TYPE": "PAYLOAD", "RCS_SIZE": "LARGE", "COUNTRY_CODE": "US", "LAUNCH_DATE": "2019-05-24", "SITE": "AFETR", "DECAY_DATE": null, "DECAYED": 0, "FILE": 2768931, "GP_ID": 155985469, "TLE_LINE0": "0 STARLINK-30", "TLE_LINE1": "1 44244U 19029K 20171.83334491 .00002829 00000-0 70479-4 0 9997", "TLE_LINE2": "2 44244 52.9964 195.8544 0001250 108.6906 109.3199 15.43862877 59477" }, "version": "v0.9", "launch": "5eb87d30ffd86e000604b378", "longitude": 10.551678198548517, "latitude": 8.26018124742001, "height_km": 434.5577668080887, "velocity_kms": 7.653046786650296, "id": "5eed770f096e59000698560d" }, ... ], "totalDocs": 537, "offset": 0, "limit": 10, "totalPages": 54, "page": 1, "pagingCounter": 1, "hasPrevPage": false, "hasNextPage": true, "prevPage": null, "nextPage": 2 } ``` ## Error Responses **Code** : `400 Bad Request` **Content** : Mongoose error is shown, with suggestions to fix the query. ================================================ FILE: docs/starlink/v4/schema.md ================================================ # Starlink Schema ```json { "version": { "type": "String", "default": null }, "launch": { "type": "UUID", "ref": "Launch", "default": null }, "longitude": { "type": "Number", "default": null, }, "latitude": { "type": "Number", "default": null, }, "height_km": { "type": "Number", "default": null, }, "velocity_kms": { "type": "Number", "default": null, }, "spaceTrack": { "CCSDS_OMM_VERS": { "type": "String", "default": null }, "COMMENT": { "type": "String", "default": null }, "CREATION_DATE": { "type": "String", "default": null }, "ORIGINATOR": { "type": "String", "default": null }, "OBJECT_NAME": { "type": "String", "default": null }, "OBJECT_ID": { "type": "String", "default": null }, "CENTER_NAME": { "type": "String", "default": null }, "REF_FRAME": { "type": "String", "default": null }, "TIME_SYSTEM": { "type": "String", "default": null }, "MEAN_ELEMENT_THEORY": { "type": "String", "default": null }, "EPOCH": { "type": "String", "default": null }, "MEAN_MOTION": { "type": "Number", "default": null }, "ECCENTRICITY": { "type": "Number", "default": null }, "INCLINATION": { "type": "Number", "default": null }, "RA_OF_ASC_NODE": { "type": "Number", "default": null }, "ARG_OF_PERICENTER": { "type": "Number", "default": null }, "MEAN_ANOMALY": { "type": "Number", "default": null }, "EPHEMERIS_TYPE": { "type": "Number", "default": null }, "CLASSIFICATION_TYPE": { "type": "String", "default": null }, "NORAD_CAT_ID": { "type": "Number", "default": null }, "ELEMENT_SET_NO": { "type": "Number", "default": null }, "REV_AT_EPOCH": { "type": "Number", "default": null }, "BSTAR": { "type": "Number", "default": null }, "MEAN_MOTION_DOT": { "type": "Number", "default": null }, "MEAN_MOTION_DDOT": { "type": "Number", "default": null }, "SEMIMAJOR_AXIS": { "type": "Number", "default": null }, "PERIOD": { "type": "Number", "default": null }, "APOAPSIS": { "type": "Number", "default": null }, "PERIAPSIS": { "type": "Number", "default": null }, "OBJECT_TYPE": { "type": "String", "default": null }, "RCS_SIZE": { "type": "String", "default": null }, "COUNTRY_CODE": { "type": "String", "default": null }, "LAUNCH_DATE": { "type": "String", "default": null }, "SITE": { "type": "String", "default": null }, "DECAY_DATE": { "type": "String", "default": null }, "DECAYED": { "type": "Number", "default": null }, "FILE": { "type": "Number", "default": null }, "GP_ID": { "type": "Number", "default": null }, "TLE_LINE0": { "type": "String", "default": null }, "TLE_LINE1": { "type": "String", "default": null }, "TLE_LINE2": { "type": "String", "default": null } } } ``` ================================================ FILE: jobs/capsules.js ================================================ import got from 'got'; import { load } from 'cheerio'; import { logger } from '../middleware/index.js'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.CAPSULES_HEALTHCHECK; const REDDIT_CAPSULES = 'https://old.reddit.com/r/spacex/wiki/capsules'; /** * Update capsule landings/reuse count * @return {Promise} */ export default async () => { try { const capsules = await got.post(`${API}/capsules/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const result = await got(REDDIT_CAPSULES); const $ = load(result.body); const v1Capsules = $('div.md:nth-child(2) > table:nth-child(8) > tbody:nth-child(2)').text(); const v1CapsuleRow = v1Capsules.split('\n').filter((v) => v !== ''); const v1CapsuleIds = v1CapsuleRow.filter((value, index) => index % 7 === 0); if (!v1CapsuleIds.length) { throw new Error('No v1 capsules found'); } const v1CapsuleStatus = v1CapsuleRow.filter((value, index) => (index + 1) % 7 === 0).map((x) => x.replace(/\[source\]/gi, '')); const v2Capsules = $('div.md:nth-child(2) > table:nth-child(10) > tbody:nth-child(2)').text(); const v2CapsuleRow = v2Capsules.split('\n').filter((v) => v !== ''); const v2CapsuleIds = v2CapsuleRow.filter((value, index) => index % 8 === 0).map((x) => x.split(',')[0]); if (!v2CapsuleIds.length) { throw new Error('No v2 capsules found'); } const v2CapsuleStatus = v2CapsuleRow.filter((value, index) => (index + 1) % 8 === 0).map((x) => x.replace(/\[source\]/gi, '')); const capsuleIds = [...v1CapsuleIds, ...v2CapsuleIds]; const capsuleStatus = [...v1CapsuleStatus, ...v2CapsuleStatus]; const updates = capsules.docs.map(async (capsule) => { const waterLandings = await got.post(`${API}/payloads/query`, { json: { query: { 'dragon.capsule': capsule.id, 'dragon.water_landing': true, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const landLandings = await got.post(`${API}/payloads/query`, { json: { query: { 'dragon.capsule': capsule.id, 'dragon.land_landing': true, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const index = capsuleIds.findIndex((id) => id === capsule.serial); await got.patch(`${API}/capsules/${capsule.id}`, { json: { reuse_count: (capsule.launches.length > 0) ? capsule.launches.length - 1 : 0, water_landings: waterLandings.totalDocs, land_landings: landLandings.totalDocs, last_update: capsuleStatus[parseInt(index, 10)], }, headers: { 'spacex-key': KEY, }, }); }); await Promise.all(updates); logger.info('Capsules updated'); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Capsules Error: ${error.message}`); } }; ================================================ FILE: jobs/cores.js ================================================ import got from 'got'; import { load } from 'cheerio'; import { logger } from '../middleware/index.js'; const REDDIT_CORES = 'https://old.reddit.com/r/spacex/wiki/cores'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.CORES_HEALTHCHECK; /** * Update cores * @return {Promise} */ export default async () => { try { const cores = await got.post(`${API}/cores/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const result = await got(REDDIT_CORES); const $ = load(result.body); // Active Cores Table const scrapedActive = []; $('div.md:nth-child(2) > table:nth-child(13) > tbody:nth-child(2) > tr').each((index, element) => { if (index === 0) return true; const tds = $(element).find('td'); const coreSerial = $(tds[0]).text() || null; const coreStatus = $(tds[5]).text().replace(/\[source\]/gi, '').trim() || null; if (!coreSerial && !coreStatus) return true; const tableRow = { coreSerial, coreStatus, }; return scrapedActive.push(tableRow); }); if (!scrapedActive.length) { throw new Error('No active cores found'); } const activeUpdates = scrapedActive.map(async (row) => { const coreId = cores.docs.find((core) => core.serial === row.coreSerial); if (coreId?.id) { await got.patch(`${API}/cores/${coreId.id}`, { json: { last_update: row.coreStatus, status: 'active', }, headers: { 'spacex-key': KEY, }, }); } }); await Promise.all(activeUpdates); logger.info('Active cores updated'); const inactive = $('div.md:nth-child(2) > table:nth-child(16) > tbody:nth-child(2)').text(); const inactiveRow = inactive.split('\n').filter((v) => v !== ''); const inactiveCores = inactiveRow.filter((value, index) => index % 6 === 0); if (!inactiveCores.length) { throw new Error('No inactive cores found'); } const inactiveStatus = inactiveRow.filter((value, index) => (index + 1) % 6 === 0).map((x) => x.replace(/\[source\]/gi, '')); const inactiveUpdates = inactiveCores.map(async (coreSerial, index) => { const coreId = cores.docs.find((core) => core.serial === coreSerial); if (coreId?.id) { await got.patch(`${API}/cores/${coreId.id}`, { json: { last_update: inactiveStatus[parseInt(index, 10)], status: 'inactive', }, headers: { 'spacex-key': KEY, }, }); } }); await Promise.all(inactiveUpdates); logger.info('Inactive cores updated'); const lost = $('div.md:nth-child(2) > table:nth-child(20) > tbody:nth-child(2)').text(); const lostRow = lost.split('\n').filter((v) => v !== ''); const lostCores = lostRow.filter((value, index) => index % 7 === 0); if (!lostCores.length) { throw new Error('No lost cores found'); } const lostStatus = lostRow.filter((value, index) => (index + 1) % 7 === 0).map((x) => x.replace(/\[source\]/gi, '')); const lostUpdates = lostCores.map(async (coreSerial, index) => { const coreId = cores.docs.find((core) => core.serial === coreSerial); if (coreId?.id) { let status; if (lostStatus[parseInt(index, 10)].match(/expended/i)) { status = 'expended'; } else { status = 'lost'; } await got.patch(`${API}/cores/${coreId.id}`, { json: { last_update: lostStatus[parseInt(index, 10)], status, }, headers: { 'spacex-key': KEY, }, }); } }); await Promise.all(lostUpdates); logger.info('Lost cores updated'); const reuseUpdates = cores.docs.map(async (core) => { if (!core?.id) return; const [rtlsAttempts, rtlsLandings, asdsAttempts, asdsLandings] = await Promise.all([ got.post(`${API}/launches/query`, { json: { query: { upcoming: false, cores: { $elemMatch: { core: core.id, landing_type: 'RTLS', landing_attempt: true, }, }, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', throwHttpErrors: false, }), got.post(`${API}/launches/query`, { json: { query: { upcoming: false, cores: { $elemMatch: { core: core.id, landing_type: 'RTLS', landing_attempt: true, landing_success: true, }, }, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', throwHttpErrors: false, }), got.post(`${API}/launches/query`, { json: { query: { upcoming: false, cores: { $elemMatch: { core: core.id, landing_type: 'ASDS', landing_attempt: true, landing_success: true, }, }, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', throwHttpErrors: false, }), got.post(`${API}/launches/query`, { json: { query: { upcoming: false, cores: { $elemMatch: { core: core.id, landing_type: 'ASDS', landing_attempt: true, landing_success: true, }, }, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', throwHttpErrors: false, }), ]); await got.patch(`${API}/cores/${core.id}`, { json: { reuse_count: (core.launches.length > 0) ? core.launches.length - 1 : 0, rtls_attempts: rtlsAttempts.totalDocs, rtls_landings: rtlsLandings.totalDocs, asds_attempts: asdsAttempts.totalDocs, asds_landings: asdsLandings.totalDocs, }, headers: { 'spacex-key': KEY, }, }); }); await Promise.all(reuseUpdates); logger.info('Core reuse updated'); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Cores Error: ${error.message}`); } }; ================================================ FILE: jobs/landpads.js ================================================ import got from 'got'; import { logger } from '../middleware/index.js'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.LANDPADS_HEALTHCHECK; /** * Update landpad attempts/successes * @return {Promise} */ export default async () => { try { const landpads = await got.post(`${API}/landpads/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const updates = landpads.docs.map(async (landpad) => { const [attempts, successes] = await Promise.all([ got.post(`${API}/launches/query`, { json: { query: { cores: { $elemMatch: { landpad: landpad.id, landing_attempt: true, }, }, upcoming: false, success: true, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }), got.post(`${API}/launches/query`, { json: { query: { cores: { $elemMatch: { landpad: landpad.id, landing_attempt: true, landing_success: true, }, }, upcoming: false, success: true, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }), ]); await got.patch(`${API}/landpads/${landpad.id}`, { json: { landing_attempts: attempts.totalDocs, landing_successes: successes.totalDocs, }, headers: { 'spacex-key': KEY, }, }); }); await Promise.all(updates); logger.info('Landpads updated'); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Landpads Error: ${error.message}`); } }; ================================================ FILE: jobs/launch-library.js ================================================ import got from 'got'; import moment from 'moment-timezone'; import { fail, success } from '../lib/healthchecks/index.js'; import { logger } from '../middleware/index.js'; const { SPACEX_KEY, LAUNCH_LIBRARY_HEALTHCHECK, SPACEX_API: API, } = process.env; const LAUNCH_LIBRARY_API = 'https://ll.thespacedevs.com/2.2.0/launch/upcoming'; /** * Attach Launch Library v2 launch id's to upcoming launches * @return {Promise} */ export default async () => { try { const log = { name: 'launch-library', updated: false, }; const upcomingLaunches = await got.post(`${API}/launches/query`, { json: { query: { upcoming: true, }, options: { sort: { flight_number: 'asc', }, limit: 1, }, }, resolveBodyOnly: true, responseType: 'json', throwHttpErrors: false, }); if (upcomingLaunches.docs.length === 1) { const llLaunches = await got(LAUNCH_LIBRARY_API, { searchParams: { lsp__name: 'SpaceX', ordering: 'net', format: 'json', }, responseType: 'json', throwHttpErrors: false, }); if (llLaunches.statusCode === 200 && llLaunches.body.results.length) { const upcomingLaunch = upcomingLaunches.docs[0]; const dates = llLaunches.body.results.map((result) => ({ llDate: result.net, llId: result.id, })); const diffs = dates.map((date) => ({ diff: moment(upcomingLaunch.date_utc).diff(moment(date.llDate)), llId: date.llId, })); // Sort the date diffs by closeness to zero const close = diffs.reduce((a, b) => (Math.abs(b.diff - 0) < Math.abs(a.diff - 0) ? b : a)); await got.patch(`${API}/launches/${upcomingLaunch.id}`, { json: { launch_library_id: close.llId, }, headers: { 'spacex-key': SPACEX_KEY, }, }); log.spacexdataName = upcomingLaunch.name; log.launch_library_id = close.llId; log.updated = true; } } await success(LAUNCH_LIBRARY_HEALTHCHECK, log); logger.info(log); } catch (error) { const formatted = { name: 'launch-library', error: error.message, stack: error.stack, }; await fail(LAUNCH_LIBRARY_HEALTHCHECK, formatted); logger.error(formatted); } }; ================================================ FILE: jobs/launches.js ================================================ import _ from 'lodash'; import got from 'got'; import { logger } from '../middleware/index.js'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.LAUNCHES_HEALTHCHECK; /** * Update launch arrays * @return {Promise} */ export default async () => { try { const launches = await got.post(`${API}/launches/query`, { json: { query: { upcoming: false, }, options: { sort: { flight_number: 'asc', }, pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const results = { capsule: false, core: false, crew: false, landpad: false, launchpad: false, payload: false, ship: false, rockets: false, }; // Update capsule launches const capsules = await got.post(`${API}/capsules/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const capsuleLaunches = capsules.docs.map(async (capsule) => { const launchIds = launches.docs .filter((launch) => launch.capsules.includes(capsule.id)) .map(({ id }) => id); await got.patch(`${API}/capsules/${capsule.id}`, { json: { launches: launchIds, }, headers: { 'spacex-key': KEY, }, }); results.capsule = true; }); await Promise.all(capsuleLaunches); // Update core launches const cores = await got.post(`${API}/cores/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const coreLaunches = cores.docs.map(async (core) => { const launchIds = launches.docs .filter((launch) => launch.cores.find((c) => c.core === core.id)) .map(({ id }) => id); await got.patch(`${API}/cores/${core.id}`, { json: { launches: launchIds, }, headers: { 'spacex-key': KEY, }, }); results.core = true; }); await Promise.all(coreLaunches); // Update crew launches const crewMembers = await got.post(`${API}/crew/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const crewLaunches = crewMembers.docs.map(async (crew) => { const launchIds = launches.docs .filter((launch) => launch.crew.includes(crew.id)) .map(({ id }) => id); await got.patch(`${API}/crew/${crew.id}`, { json: { launches: launchIds, }, headers: { 'spacex-key': KEY, }, }); results.crew = true; }); await Promise.all(crewLaunches); // Update landpad launches const landpads = await got.post(`${API}/landpads/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const landpadLaunches = landpads.docs.map(async (landpad) => { const launchIds = launches.docs .filter((launch) => launch.cores.find((c) => c.landpad === landpad.id)) .map(({ id }) => id); await got.patch(`${API}/landpads/${landpad.id}`, { json: { launches: launchIds, }, headers: { 'spacex-key': KEY, }, }); results.landpad = true; }); await Promise.all(landpadLaunches); // Update launchpad launches const launchpads = await got.post(`${API}/launchpads/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const launchpadLaunches = launchpads.docs.map(async (launchpad) => { const launchIds = launches.docs .filter((launch) => launch.launchpad === launchpad.id) .map(({ id }) => id); await got.patch(`${API}/launchpads/${launchpad.id}`, { json: { launches: launchIds, }, headers: { 'spacex-key': KEY, }, }); results.launchpad = true; }); await Promise.all(launchpadLaunches); // Update payload launches const payloads = await got.post(`${API}/payloads/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const payloadLaunches = payloads.docs.map(async (payload) => { const launchId = _.find(launches.docs, (launch) => launch.payloads.includes(payload.id)); if (launchId?.id) { await got.patch(`${API}/payloads/${payload.id}`, { json: { launch: launchId.id, }, headers: { 'spacex-key': KEY, }, }); results.payload = true; } }); await Promise.all(payloadLaunches); // Update ship launches const ships = await got.post(`${API}/ships/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const shipLaunches = ships.docs.map(async (ship) => { const launchIds = launches.docs .filter((launch) => launch.ships.includes(ship.id)) .map(({ id }) => id); await got.patch(`${API}/ships/${ship.id}`, { json: { launches: launchIds, }, headers: { 'spacex-key': KEY, }, }); results.ship = true; }); await Promise.all(shipLaunches); // Update rocket success percentage const rockets = await got.post(`${API}/rockets/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const rocketSuccess = rockets.docs.map(async (rocket) => { const successes = launches?.docs .filter((l) => (l.rocket === rocket.id) && (l.success === true))?.length ?? 0; const attempts = launches?.docs .filter((l) => l.rocket === rocket.id)?.length ?? 0; if (attempts > 0) { const successRate = parseInt(Math.round((successes / attempts) * 100), 10); await got.patch(`${API}/rockets/${rocket.id}`, { json: { success_rate_pct: successRate, }, headers: { 'spacex-key': KEY, }, }); } results.rockets = true; }); await Promise.all(rocketSuccess); logger.info(results); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Launches Error: ${error.message}`); } }; ================================================ FILE: jobs/launchpads.js ================================================ import got from 'got'; import { logger } from '../middleware/index.js'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.LAUNCHPADS_HEALTHCHECK; /** * Update launchpad attempts/successes * @return {Promise} */ export default async () => { try { const launchpads = await got.post(`${API}/launchpads/query`, { json: { options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); const updates = launchpads.docs.map(async (launchpad) => { const [attempts, successes] = await Promise.all([ got.post(`${API}/launches/query`, { json: { query: { launchpad: launchpad.id, upcoming: false, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }), got.post(`${API}/launches/query`, { json: { query: { launchpad: launchpad.id, upcoming: false, success: true, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }), ]); await got.patch(`${API}/launchpads/${launchpad.id}`, { json: { launch_attempts: attempts.totalDocs, launch_successes: successes.totalDocs, }, headers: { 'spacex-key': KEY, }, }); }); await Promise.all(updates); logger.info('Launchpads updated'); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Launchpads Error: ${error.message}`); } }; ================================================ FILE: jobs/payloads.js ================================================ import got from 'got'; import { CookieJar } from 'tough-cookie'; import { logger } from '../middleware/index.js'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.PAYLOADS_HEALTHCHECK; /** * Update payload orbit params * @return {Promise} */ export default async () => { try { const cookieJar = new CookieJar(); const [payloads] = await Promise.all([ got.post(`${API}/payloads/query`, { json: { query: {}, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }), got.post('https://www.space-track.org/ajaxauth/login', { form: { identity: process.env.SPACEX_TRACK_LOGIN, password: process.env.SPACEX_TRACK_PASSWORD, }, cookieJar, }), ]); const data = await got('https://www.space-track.org/basicspacedata/query/class/tle_latest/ORDINAL/1/orderby/NORAD_CAT_ID/epoch/>now-45/format/json', { resolveBodyOnly: true, responseType: 'json', cookieJar, }); const updates = payloads.docs.map(async (payload) => { const noradId = payload.norad_ids.shift() ?? null; const specificOrbit = data.find((sat) => parseInt(sat.NORAD_CAT_ID, 10) === noradId); if (specificOrbit) { await got.patch(`${API}/payloads/${payload.id}`, { json: { epoch: new Date(Date.parse(specificOrbit.EPOCH)).toISOString(), mean_motion: parseFloat(specificOrbit.MEAN_MOTION), raan: parseFloat(specificOrbit.RA_OF_ASC_NODE), arg_of_pericenter: parseFloat(specificOrbit.ARG_OF_PERICENTER), mean_anomaly: parseFloat(specificOrbit.MEAN_ANOMALY), semi_major_axis_km: parseFloat(specificOrbit.SEMIMAJOR_AXIS), eccentricity: parseFloat(specificOrbit.ECCENTRICITY), periapsis_km: parseFloat(specificOrbit.PERIGEE), apoapsis_km: parseFloat(specificOrbit.APOGEE), inclination_deg: parseFloat(specificOrbit.INCLINATION), period_min: parseFloat(specificOrbit.PERIOD), }, headers: { 'spacex-key': KEY, }, }); } }); await Promise.all(updates); logger.info({ orbitsUpdated: true, }); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Payloads Error: ${error.message}`); } }; ================================================ FILE: jobs/roadster.js ================================================ import got from 'got'; import moment from 'moment-timezone'; import { logger } from '../middleware/index.js'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.ROADSTER_HEALTHCHECK; /** * This script gathers tesla roadster orbital data from JPL Horizons, * parses the output with various regular expressions, and updates * the data accordingly. * See https://ssd-api.jpl.nasa.gov/doc/horizons.html for more information * @return {Promise} */ export default async () => { // Using date range so Horizons doesn't give us the default 10 day data const today = moment().format('YYYY-MMM-DD HH:mm:ss'); const tomorrow = moment().add(1, 'day').format('YYYY-MMM-DD HH:mm:ss'); const ORBIT_URL = `https://ssd.jpl.nasa.gov/api/horizons.api?format=text&COMMAND='-143205'&CENTER='500@10'&EPHEM_TYPE='ELEMENTS'&START_TIME='${today}'&STOP_TIME='${tomorrow}'&STEP_SIZE='1 d'&OUT_UNITS='AU-D'&REF_PLANE='ECLIPTIC'&REF_SYSTEM='J2000'&TP_TYPE='ABSOLUTE'&ELEM_LABELS='YES'&CSV_FORMAT='NO'`; const EARTH_DIST_URL = `https://ssd.jpl.nasa.gov/api/horizons.api?format=text&COMMAND='-143205'&CENTER='500@399'&START_TIME='${today}'&STOP_TIME='${tomorrow}'&STEP_SIZE='1 d'&CAL_FORMAT='CAL'&TIME_DIGITS='MINUTES'&ANG_FORMAT='HMS'&OUT_UNITS='KM-S'&RANGE_UNITS='AU'&APPARENT='AIRLESS'&SUPPRESS_RANGE_RATE='NO'&SKIP_DAYLT='NO'&EXTRA_PREC='NO'&R_T_S_ONLY='NO'&REF_SYSTEM='J2000'&CSV_FORMAT='NO'&QUANTITIES='19,20'`; const MARS_DIST_URL = `https://ssd.jpl.nasa.gov/api/horizons.api?format=text&COMMAND='-143205'&CENTER='500@499'&START_TIME='${today}'&STOP_TIME='${tomorrow}'&STEP_SIZE='1 d'&CAL_FORMAT='CAL'&TIME_DIGITS='MINUTES'&ANG_FORMAT='HMS'&OUT_UNITS='KM-S'&RANGE_UNITS='AU'&APPARENT='AIRLESS'&SUPPRESS_RANGE_RATE='NO'&SKIP_DAYLT='NO'&EXTRA_PREC='NO'&R_T_S_ONLY='NO'&REF_SYSTEM='J2000'&CSV_FORMAT='NO'&QUANTITIES='19,20,22'`; try { const params = { resolveBodyOnly: true, }; const [orbitParams, earthDist, marsDist] = await Promise.all([ got(ORBIT_URL, params), got(EARTH_DIST_URL, params), got(MARS_DIST_URL, params), ]); /** * All JPL Horizon parsing regexes from https://github.com/lnxbil/stellarium-comet-jpl */ // Reading the SOE-Part into 'soe' for further processing const soeReg = /\$\$SOE([\s\S]*)\$\$EOE/m; soeReg.exec(orbitParams); const orbitSoe = RegExp.$1; // Reading Epoch - valid for CE dates. const epochReg = /^[ ]*([0-9.]+)[ ]*=[ ]*A[.]D[.][ ]*/m; epochReg.exec(orbitSoe); const epoch = parseFloat(RegExp.$1); // Reading Semi-major axis (A) const smaReg = /A=[ ]*([0-9.E+-]+)/m; smaReg.exec(orbitSoe); const sma = parseFloat(RegExp.$1); // Reading Eccentricity (EC) const ecReg = /EC=[ ]*([0-9.E+-]+)/m; ecReg.exec(orbitSoe); const ecc = parseFloat(RegExp.$1); // Reading Periapsis distance (QR) const qrReg = /QR=[ ]*([0-9.E+-]+)/m; qrReg.exec(orbitSoe); const pqr = parseFloat(RegExp.$1); // Reading Apoapsis distance (QR) const adReg = /AD=[ ]*([0-9.E+-]+)/m; adReg.exec(orbitSoe); const aad = parseFloat(RegExp.$1); // Reading Longitude of Ascending Node const omReg = /OM=[ ]*([0-9.E+-]+)/m; omReg.exec(orbitSoe); const lon = parseFloat(RegExp.$1); // Reading Argument of periapsis (W) const wReg = /W\s=[ ]*([0-9.E+-]+)/m; wReg.exec(orbitSoe); const aop = parseFloat(RegExp.$1); // Reading Inclination w.r.t xy-plane const inReg = /IN=[ ]*([0-9.E+-]+)/m; inReg.exec(orbitSoe); const inc = parseFloat(RegExp.$1); // Reading orbital period const periodReg = /PR=[ ]*([0-9.E+-]+)/m; periodReg.exec(orbitSoe); const period = parseFloat(RegExp.$1); // Read SOE of earth distance + calculate distance in miles and kilometers soeReg.exec(earthDist); const earthSoe = RegExp.$1; const strippedEarth = earthSoe.replace(/(\r\n\t|\n|\r\t)/gm, ''); const earthResult = strippedEarth.split(/\s+/)[5]; const earthDistanceKm = (parseFloat(earthResult.trim()) * 149598073); const earthDistanceMi = earthDistanceKm * 0.621371; // Read SOE of mars distance + calculate distance in miles and kilometers soeReg.exec(marsDist); const marsSoe = RegExp.$1; const strippedMars = marsSoe.replace(/(\r\n\t|\n|\r\t)/gm, ''); const marsResult = strippedMars.split(/\s+/)[5]; const marsDistanceKm = (parseFloat(marsResult.trim()) * 149598073); const marsDistanceMi = marsDistanceKm * 0.621371; // Read SOE of orbital speed in KM/s + calculate kph and mph const speedResult = strippedMars.split(/\s+/)[5]; const orbitalSpeedKph = (parseFloat(speedResult.trim()) * 60.0 * 60.0); const orbitalSpeedMph = orbitalSpeedKph * 0.621371; const roadster = await got(`${API}/roadster`, { resolveBodyOnly: true, responseType: 'json', }); await got.patch(`${API}/roadster/${roadster.id}`, { json: { epoch_jd: epoch, apoapsis_au: aad, periapsis_au: pqr, semi_major_axis_au: sma, eccentricity: ecc, inclination: inc, longitude: lon, periapsis_arg: aop, period_days: period, speed_kph: orbitalSpeedKph, speed_mph: orbitalSpeedMph, earth_distance_km: earthDistanceKm, earth_distance_mi: earthDistanceMi, mars_distance_km: marsDistanceKm, mars_distance_mi: marsDistanceMi, }, headers: { 'spacex-key': KEY, }, }); logger.info('Roadster updated'); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Roadster Error: ${error.message}`); } }; ================================================ FILE: jobs/starlink.js ================================================ import got from 'got'; import { CookieJar } from 'tough-cookie'; import Moment from 'moment-timezone'; import MomentRange from 'moment-range'; import { getSatelliteInfo } from 'tle.js'; import { logger } from '../middleware/index.js'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.STARLINK_HEALTHCHECK; const moment = MomentRange.extendMoment(Moment); /** * Generate Starlink version from date * @param {Date} date Launch date UTC * @param {String} name Mission name * @return {String} */ const starlinkVersion = (date, name) => { if (!date || !name) { return null; } const missionNameVersion = name.match(/(?v\d{1,3}.\d{1,3})/i)?.groups?.version; if (missionNameVersion) { return missionNameVersion; } const parsedDate = moment(date); let version = null; if (parsedDate.isAfter('2019-11-11')) { version = 'v1.0'; } else if (parsedDate.isAfter('2019-05-24')) { version = 'v0.9'; } else if (parsedDate.isAfter('2018-02-22')) { version = 'prototype'; } return version; }; /** * Update Starlink orbits * @return {Promise} */ export default async () => { try { const cookieJar = new CookieJar(); await got.post('https://www.space-track.org/ajaxauth/login', { form: { identity: process.env.SPACEX_TRACK_LOGIN, password: process.env.SPACEX_TRACK_PASSWORD, }, cookieJar, }); const data = await got('https://www.space-track.org/basicspacedata/query/class/gp/OBJECT_NAME/~~STARLINK,~~TINTIN/orderby/NORAD_CAT_ID', { responseType: 'json', timeout: { request: 480000, // 8 minutes }, cookieJar, }); const starlinkSats = data.body.filter((sat) => /starlink|tintin/i.test(sat.OBJECT_NAME)); const updates = starlinkSats.map(async (sat) => { const date = moment.utc(sat.LAUNCH_DATE, 'YYYY-MM-DD'); const range = date.range('day'); const launches = await got.post(`${API}/launches/query`, { json: { query: { date_utc: { $gte: range.start.toISOString(), $lte: range.end.toISOString(), }, }, options: { pagination: false, }, }, resolveBodyOnly: true, responseType: 'json', }); let position; if (!(sat.DECAY_DATE)) { const tle = [sat.TLE_LINE1, sat.TLE_LINE2]; try { position = await getSatelliteInfo(tle); } catch (error) { console.log(error); } } await got.patch(`${API}/starlink/${sat.NORAD_CAT_ID}`, { json: { version: starlinkVersion( launches?.docs[0]?.date_utc ?? null, launches?.docs[0]?.name ?? null, ), launch: launches?.docs[0]?.id ?? null, longitude: position?.lng ?? null, latitude: position?.lat ?? null, height_km: position?.height ?? null, velocity_kms: position?.velocity ?? null, spaceTrack: { CCSDS_OMM_VERS: sat.CCSDS_OMM_VERS, COMMENT: sat.COMMENT, CREATION_DATE: sat.CREATION_DATE, ORIGINATOR: sat.ORIGINATOR, OBJECT_NAME: sat.OBJECT_NAME, OBJECT_ID: sat.OBJECT_ID, CENTER_NAME: sat.CENTER_NAME, REF_FRAME: sat.REF_FRAME, TIME_SYSTEM: sat.TIME_SYSTEM, MEAN_ELEMENT_THEORY: sat.MEAN_ELEMENT_THEORY, EPOCH: sat.EPOCH, MEAN_MOTION: sat.MEAN_MOTION, ECCENTRICITY: sat.ECCENTRICITY, INCLINATION: sat.INCLINATION, RA_OF_ASC_NODE: sat.RA_OF_ASC_NODE, ARG_OF_PERICENTER: sat.ARG_OF_PERICENTER, MEAN_ANOMALY: sat.MEAN_ANOMALY, EPHEMERIS_TYPE: sat.EPHEMERIS_TYPE, CLASSIFICATION_TYPE: sat.CLASSIFICATION_TYPE, NORAD_CAT_ID: sat.NORAD_CAT_ID, ELEMENT_SET_NO: sat.ELEMENT_SET_NO, REV_AT_EPOCH: sat.REV_AT_EPOCH, BSTAR: sat.BSTAR, MEAN_MOTION_DOT: sat.MEAN_MOTION_DOT, MEAN_MOTION_DDOT: sat.MEAN_MOTION_DDOT, SEMIMAJOR_AXIS: sat.SEMIMAJOR_AXIS, PERIOD: sat.PERIOD, APOAPSIS: sat.APOAPSIS, PERIAPSIS: sat.PERIAPSIS, OBJECT_TYPE: sat.OBJECT_TYPE, RCS_SIZE: sat.RCS_SIZE, COUNTRY_CODE: sat.COUNTRY_CODE, LAUNCH_DATE: sat.LAUNCH_DATE, SITE: sat.SITE, DECAY_DATE: sat.DECAY_DATE, DECAYED: !!(sat.DECAY_DATE), FILE: sat.FILE, GP_ID: sat.GP_ID, TLE_LINE0: sat.TLE_LINE0, TLE_LINE1: sat.TLE_LINE1, TLE_LINE2: sat.TLE_LINE2, }, }, headers: { 'spacex-key': KEY, }, }); }); await Promise.all(updates); logger.info({ starlinkUpdated: true, }); if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.error(error); console.log(`Starlink Error: ${error.message}`); } }; ================================================ FILE: jobs/upcoming.js ================================================ /* eslint-disable no-continue */ /* eslint-disable no-restricted-syntax */ import got from 'got'; import { load } from 'cheerio'; import * as fuzz from 'fuzzball'; import moment from 'moment-timezone'; import { logger } from '../middleware/index.js'; const REDDIT_WIKI = 'https://old.reddit.com/r/spacex/wiki/launches/manifest'; const API = process.env.SPACEX_API; const KEY = process.env.SPACEX_KEY; const HEALTHCHECK = process.env.UPCOMING_HEALTHCHECK; /** * This script gathers dates and payload names from the subreddit launch wiki, * fuzzy checks them against existing upcoming mission names and updates the date if a * change is made in the wiki. The proper time zone is calculated from the launch site * id of the launch. It also corrects the flight number order based on the launch wiki order. * @return {Promise} */ export default async () => { try { const flightNumbers = []; const rawLaunches = await got.post(`${API}/launches/query`, { json: { options: { pagination: false, sort: { flight_number: 'asc', }, }, }, resolveBodyOnly: true, responseType: 'json', }); // Past launches needed to set new flight number order const upcoming = rawLaunches.docs.filter((doc) => doc.upcoming === true); const past = rawLaunches.docs.filter((doc) => doc.upcoming === false); // Grab subreddit wiki const rawWiki = await got(REDDIT_WIKI, { resolveBodyOnly: true, }); const $ = load(rawWiki); const wiki = $('body > div.content > div > div > table:nth-child(7) > tbody').text(); if (!wiki) { throw new Error(`Broken wiki selector: ${wiki}`); } const wikiRow = wiki.split('\n').filter((v) => v !== ''); const allWikiDates = wikiRow.filter((_, index) => index % 7 === 0); const wikiDates = allWikiDates.slice(0, 30).map((date) => date .replace(/(?<=\[[0-9]{2}:[0-9]{2}\])(\[[0-9]{1,3}\]|\[[0-9]{1,3}|[0-9]{1,3}\])*/gi, '') .replace(/(?<=\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*)([0-9]{1,3}\])*/gi, '') .replace(/~|(\[|\[\[)[0-9]{1,3}\]/gi, '') .replace(/~|(\[|\])/gi, '') .replace(/(early|mid|late|end|tbd|tba|net)/gi, ' ') .replace(/-[0-9]{2}:[0-9]{2}/gi, ' ') .replace(/(\(|\)|\?)/gi, ' ') // Removes (?) from dates .split('/')[0].trim()); const rawWikiDates = allWikiDates.slice(0, 30); const allWikiPayloads = wikiRow.filter((_, index) => (index + 2) % 7 === 0); const wikiPayloads = allWikiPayloads.slice(0, 30).map((payload) => payload.replace(/\[[0-9]{1,3}\]/gi, '')); const allWikiLaunchpads = wikiRow.filter((_, index) => (index + 5) % 7 === 0); const wikiLaunchpads = allWikiLaunchpads.slice(0, 30).map((launchpad) => launchpad.replace(/\[[0-9]{1,3}\]/gi, '')); // Set base flight number to automatically reorder launches on the wiki // If the most recent past launch is still on the wiki, don't offset the flight number let baseFlightNumber; if (fuzz.partial_ratio(past[past.length - 1].name, wikiPayloads[0]) === 100) { baseFlightNumber = past[past.length - 1].flight_number; } else { baseFlightNumber = past[past.length - 1].flight_number + 1; } // Compare each mission name against entire list of wiki payloads, and fuzzy match the // mission name against the wiki payload name. The partial match must be 100%, to avoid // conflicts like SSO-A and SSO-B, where a really close match would produce wrong results. for await (const [, launch] of upcoming.entries()) { // Allow users to pause auto updates from wiki, while still preserving // flight reordering feature if (!launch.auto_update) { continue; } for await (const [wikiIndex, wikiPayload] of wikiPayloads.entries()) { if (fuzz.partial_ratio(launch.name, wikiPayload) === 100) { // Special check for starlink / smallsat launches, because Starlink 2 and Starlink 23 // both pass the partial ratio check, so they are checked strictly below if (/starlink/i.test(launch.name) && fuzz.ratio(launch.name, wikiPayload) !== 100) { continue; } // Check and see if dates match a certain pattern depending on the length of the // date given. This sets the amount of precision needed for the date. // Allows for long months or short months ex: September vs Sep // Allows for time with or without brackets ex: [23:45] vs 23:45 // Anything with NET in date const netPattern = /^.*(net).*$/i; // Anything with TBD/TBA in date const tbdPattern = /^.*(tbd|tba).*$/i; // 2020 const yearPattern = /^\s*[0-9]{4}\s*$/i; // 2020 [14:10] const yearHourPattern = /^\s*[0-9]{4}\s*(\[?\s*([0-9]{2}|[0-9]{1}):[0-9]{2}\s*\]?)\s*$/i; // 2020 Nov const monthPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*$/i; // 2020 Nov 4 const dayPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*[0-9]{1,2}\s*$/i; // 2020 Nov [14:10] const vagueHourPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*(\[?\s*([0-9]{2}|[0-9]{1}):[0-9]{2}\s*\]?)\s*$/i; // 2020 Nov 4 [14:10] const hourPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*[0-9]{1,2}\s*(\[?\s*([0-9]{2}|[0-9]{1}):[0-9]{2}\s*\]?)\s*$/i; // 2020 Nov 4 [14:10:50] const secondPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*[0-9]{1,2}\s*(\[?\s*([0-9]{2}|[0-9]{1}):[0-9]{2}:[0-9]{2}\s*\]?)\s*$/i; let precision; let wikiDate = wikiDates[parseInt(wikiIndex, 10)]; const rawWikiDate = rawWikiDates[parseInt(wikiIndex, 10)]; // Check if date is NET const net = netPattern.test(rawWikiDate); // Check if date contains TBD const tbd = tbdPattern.test(rawWikiDate); // Remove extra stuff humans might add // NOTE: Add to this when people add unexpected things to dates in the wiki const cleanedwikiDate = wikiDate; // Set date precision if (cleanedwikiDate.includes('Q')) { // Quarter is first because moment.js does not make // a distinction between half vs quarter. Therefore // the first half starts at the beginning Q1, and the // second half starts at the beginning of Q3 wikiDate = wikiDate.replace('Q', ''); precision = 'quarter'; } else if (cleanedwikiDate.includes('H1')) { wikiDate = wikiDate.replace('H1', '1'); precision = 'half'; } else if (cleanedwikiDate.includes('H2')) { wikiDate = wikiDate.replace('H2', '3'); precision = 'half'; } else if (yearPattern.test(cleanedwikiDate)) { precision = 'year'; } else if (yearHourPattern.test(cleanedwikiDate)) { precision = 'year'; } else if (monthPattern.test(cleanedwikiDate)) { precision = 'month'; } else if (dayPattern.test(cleanedwikiDate)) { precision = 'day'; } else if (vagueHourPattern.test(cleanedwikiDate)) { precision = 'month'; } else if (hourPattern.test(cleanedwikiDate)) { precision = 'hour'; } else if (secondPattern.test(cleanedwikiDate)) { precision = 'hour'; } else { throw new Error(`No date match: ${cleanedwikiDate}`); } // Add flight numbers to array to check for duplicates flightNumbers.push(baseFlightNumber + wikiIndex); // Wiki launchpad matchers const slc40Pattern = /^SLC-40.*$/i; const lc39aPattern = /^LC-39A.*$/i; const slc4ePattern = /^SLC-4E.*$/i; const bcPattern = /^BC.*$/i; const unknownPattern = /^\?.*$/i; // Calculate launch site depending on wiki manifest const launchpad = wikiLaunchpads[parseInt(wikiIndex, 10)]; console.log(launchpad); let queryName; if (slc40Pattern.test(launchpad)) { queryName = 'CCSFS SLC 40'; } else if (lc39aPattern.test(launchpad)) { queryName = 'KSC LC 39A'; } else if (slc4ePattern.test(launchpad)) { queryName = 'VAFB SLC 4E'; } else if (bcPattern.test(launchpad)) { queryName = 'STLS'; } else if (unknownPattern.test(launchpad)) { queryName = 'CCSFS SLC 40'; } else { throw new Error(`No launchpad match: ${launchpad}`); } const launchpads = await got.post(`${API}/launchpads/query`, { json: { query: { name: queryName, }, options: { limit: 1, }, }, resolveBodyOnly: true, responseType: 'json', }); const launchpadId = launchpads.docs[0].id; const { timezone } = launchpads.docs[0]; // Clean wiki date, set timezone const parsedDate = `${wikiDates[parseInt(wikiIndex, 10)].replace(/(-|\[|\]|~|early|mid|late|end|net)/gi, ' ').split('/')[0].trim()}`; const time = moment(parsedDate, ['YYYY MMM HH:mm:ss', 'YYYY MMM HH:mm', 'YYYY MMM D HH:mm', 'YYYY MMM D', 'YYYY MMM', 'YYYY HH:mm', 'YYYY Q', 'YYYY']); const zone = moment.tz(time, 'UTC'); const localTime = time.tz(timezone).format(); const rawUpdate = { flight_number: (baseFlightNumber + wikiIndex), date_unix: zone.unix(), date_utc: zone.toISOString(), date_local: localTime, date_precision: precision, launchpad: launchpadId, tbd, net, }; logger.info({ launch: launch.name, ...rawUpdate, }); await got.patch(`${API}/launches/${launch.id}`, { json: { ...rawUpdate, }, headers: { 'spacex-key': KEY, }, }); } } } if (HEALTHCHECK) { await got(HEALTHCHECK); } } catch (error) { console.log(`Upcoming Launch Error: ${error.message}`); } }; ================================================ FILE: jobs/webcast.js ================================================ import got from 'got'; import * as fuzz from 'fuzzball'; import Parser from 'rss-parser'; import { fail, success } from '../lib/healthchecks/index.js'; import { logger } from '../middleware/index.js'; const YOUTUBE_PREFIX = 'https://youtu.be'; const CHANNEL_ID = 'UCtI0Hodo5o5dUb67FeUjDeA'; const { SPACEX_KEY, WEBCAST_HEALTHCHECK, SPACEX_API: API, } = process.env; /** * Check for new SpaceX webcast links * @return {Promise} */ export default async () => { try { let updated = false; let match = false; const parser = new Parser(); const url = `https://www.youtube.com/feeds/videos.xml?channel_id=${CHANNEL_ID}`; const rssResult = await got(url, { resolveBodyOnly: true, }); const result = await parser.parseString(rssResult); const latest = result.items[0]; const rssTitle = latest.title; const rssYoutubeId = latest.link.split('v=')[1]; const launches = await got.post(`${API}/launches/query`, { json: { query: { upcoming: true, }, options: { sort: { flight_number: 'asc', }, limit: 1, }, }, resolveBodyOnly: true, responseType: 'json', }); const launchId = launches.docs[0].id; const missionName = launches.docs[0].name; const ratio = fuzz.ratio(rssTitle, missionName); // Prevent matching on mission control audio videos if (!rssTitle.includes('audio') && ratio >= 50) { match = true; const pastLaunches = await got.post(`${API}/launches/query`, { json: { query: { upcoming: false, }, options: { sort: { flight_number: 'desc', }, limit: 1, }, }, resolveBodyOnly: true, responseType: 'json', }); const pastYoutubeId = pastLaunches.docs[0].links.youtube_id; if (rssYoutubeId !== pastYoutubeId) { await got.patch(`${API}/launches/${launchId}`, { json: { 'links.webcast': `${YOUTUBE_PREFIX}/${rssYoutubeId}`, 'links.youtube_id': rssYoutubeId, }, headers: { 'spacex-key': SPACEX_KEY, }, }); updated = true; } } const log = { name: 'webcast', ratio, match, updated, youtubeTitle: rssTitle, youtubeId: rssYoutubeId, }; await success(WEBCAST_HEALTHCHECK, log); logger.info(log); } catch (error) { const formatted = { name: 'webcast', error: error.message, stack: error.stack, }; await fail(WEBCAST_HEALTHCHECK, formatted); logger.error(formatted); } }; ================================================ FILE: jobs/worker.js ================================================ import { CronJob } from 'cron'; import dotenv from 'dotenv'; import { logger } from '../middleware/index.js'; import launches from './launches.js'; import payloads from './payloads.js'; import landpads from './landpads.js'; import launchpads from './launchpads.js'; import capsules from './capsules.js'; import cores from './cores.js'; import roadster from './roadster.js'; import upcoming from './upcoming.js'; import starlink from './starlink.js'; import webcast from './webcast.js'; import launchLibrary from './launch-library.js'; // Env init dotenv.config(); // Every 10 minutes const launchesJob = new CronJob('*/10 * * * *', launches); // Every 10 minutes const landpadsJob = new CronJob('*/10 * * * *', landpads); // Every 10 minutes const launchpadsJob = new CronJob('*/10 * * * *', launchpads); // Every 10 minutes const capsulesJob = new CronJob('*/10 * * * *', capsules); // Every 10 minutes const coresJob = new CronJob('*/10 * * * *', cores); // Every 10 minutes const roadsterJob = new CronJob('*/10 * * * *', roadster); // Every 10 minutes const upcomingJob = new CronJob('*/10 * * * *', upcoming); // Every hour on :25 const payloadsJob = new CronJob('25 * * * *', payloads); // Every hour on :35 const starlinkJob = new CronJob('35 * * * *', starlink); // Every 5 minutes const webcastJob = new CronJob('*/5 * * * *', webcast); // Every hour on :45 const launchLibraryJob = new CronJob('45 * * * *', launchLibrary); try { launchesJob.start(); payloadsJob.start(); landpadsJob.start(); launchpadsJob.start(); capsulesJob.start(); coresJob.start(); roadsterJob.start(); upcomingJob.start(); starlinkJob.start(); webcastJob.start(); launchLibraryJob.start(); } catch (error) { const formatted = { name: 'worker', error: error.message, stack: error.stack, }; logger.error(formatted); } ================================================ FILE: lib/constants.js ================================================ /** * Healthchecks.io API prefix */ export const HEALTHCHECK_PREFIX = 'https://hc-ping.com'; /** * Default Port */ export const DEFAULT_PORT = 6673; ================================================ FILE: lib/healthchecks/fail.js ================================================ /** * Imports */ import got from 'got'; import { HEALTHCHECK_PREFIX } from '../constants.js'; /** * Send fail signal to healthcheck.io * * @param {String} [id] UUID of healthcheck * @param {String|Object} [msg] Message to pass to healthcheck * @returns {Boolean} True if successful */ export default async (id = null, msg = {}) => { if (id) { const response = await got.post({ prefixUrl: HEALTHCHECK_PREFIX, url: `${id}/fail`, json: msg, }); if (response.statusCode === 200) { return true; } } return false; }; ================================================ FILE: lib/healthchecks/index.js ================================================ export { default as fail } from './fail.js'; export { default as start } from './start.js'; export { default as success } from './success.js'; ================================================ FILE: lib/healthchecks/start.js ================================================ /** * Imports */ import got from 'got'; import { HEALTHCHECK_PREFIX } from '../constants.js'; /** * Send start signal to healthcheck.io * * @param {String} [id] UUID of healthcheck * @param {String|Object} [msg] Message to pass to healthcheck * @returns {Boolean} True if successful */ export default async (id = null, msg = {}) => { if (id) { const response = await got.post({ prefixUrl: HEALTHCHECK_PREFIX, url: `${id}/start`, json: msg, }); if (response.statusCode === 200) { return true; } } return false; }; ================================================ FILE: lib/healthchecks/success.js ================================================ /** * Imports */ import got from 'got'; import { HEALTHCHECK_PREFIX } from '../constants.js'; /** * Send start signal to healthcheck.io * * @param {String} id UUID of healthcheck * @param {String|Object} [msg] Message to pass to healthcheck * @returns {Boolean} True if successful */ export default async (id = null, msg = {}) => { if (id) { const response = await got.post({ prefixUrl: HEALTHCHECK_PREFIX, url: `${id}`, json: msg, }); if (response.statusCode === 200) { return true; } } return false; }; ================================================ FILE: lib/utils/healthcheck.js ================================================ #!/usr/bin/env node import got from 'got'; const { HEALTH_URL, SPACEX_WORKER } = process.env; (async () => { try { if (SPACEX_WORKER) { process.exit(0); } await got(HEALTH_URL); process.exit(0); } catch (error) { console.log(error.message); process.exit(1); } })(); ================================================ FILE: middleware/auth.js ================================================ import mongoose from 'mongoose'; const db = mongoose.connection.useDb('auth', { useCache: true }); /** * Authentication middleware */ export default async (ctx, next) => { const key = ctx.request.headers['spacex-key']; if (key) { const user = await db.collection('users').findOne({ key }); if (user?.key === key) { ctx.state.roles = user.roles; await next(); return; } } ctx.status = 401; ctx.body = 'https://youtu.be/RfiQYRn7fBg'; }; ================================================ FILE: middleware/authz.js ================================================ /** * Authorization middleware * * @param {String} role Role for protected route * @returns {void} */ export default (role) => async (ctx, next) => { const { roles } = ctx.state; const allowed = roles.includes(role); if (allowed) { await next(); return; } ctx.status = 403; }; ================================================ FILE: middleware/cache.js ================================================ import Redis from 'ioredis'; import blake3 from 'blake3'; import logger from './logger.js'; const redis = (process.env.SPACEX_REDIS) ? new Redis(process.env.SPACEX_REDIS) : new Redis(); let redisAvailable = false; redis.on('error', () => { redisAvailable = false; }); redis.on('end', () => { redisAvailable = false; }); redis.on('ready', () => { redisAvailable = true; logger.info('Redis connected'); }); /** * BLAKE3 hash func for redis keys * * @param {String} str String to hash * @returns {String} Hashed result */ const hash = (str) => blake3.createHash().update(str).digest('hex'); /** * Redis cache middleware * * @param {Number} ttl Cache TTL in seconds * @returns {void} */ export default (ttl) => async (ctx, next) => { if (process.env.NODE_ENV !== 'production') { await next(); return; } if (!redisAvailable) { ctx.response.set('spacex-api-cache-online', 'false'); await next(); return; } ctx.response.set('spacex-api-cache-online', 'true'); const { url, method } = ctx.request; const key = `spacex-cache:${hash(`${method}${url}${JSON.stringify(ctx.request.body)}`)}`; if (ttl) { ctx.response.set('Cache-Control', `max-age=${ttl}`); } else { ctx.response.set('Cache-Control', 'no-store'); } // Only allow cache on whitelist methods if (!['GET', 'POST'].includes(ctx.request.method)) { await next(); return; } let cached; try { cached = await redis.get(key); if (cached) { ctx.response.status = 200; ctx.response.set('spacex-api-cache', 'HIT'); ctx.response.type = 'application/json'; ctx.response.body = cached; cached = true; } } catch (e) { cached = false; } if (cached) { return; } await next(); const responseBody = JSON.stringify(ctx.response.body); ctx.response.set('spacex-api-cache', 'MISS'); // Set cache try { if ((ctx?.response?.status !== 200) || !responseBody) { return; } await redis.set(key, responseBody, 'EX', ttl); } catch (e) { console.log(`Failed to set cache: ${e.message}`); } }; export { redis, }; ================================================ FILE: middleware/errors.js ================================================ /** * Error handler middleware * * @param {Object} ctx Koa context * @param {function} next Koa next function * @returns {void} */ export default async (ctx, next) => { try { await next(); } catch (err) { if (err?.kind === 'ObjectId') { err.status = 404; } else { ctx.status = err.status ?? 500; ctx.body = err.message; } } }; ================================================ FILE: middleware/index.js ================================================ export { default as auth } from './auth.js'; export { default as authz } from './authz.js'; export { default as cache } from './cache.js'; export { default as errors } from './errors.js'; export { default as responseTime } from './response-time.js'; export { default as logger } from './logger.js'; ================================================ FILE: middleware/logger.js ================================================ import pino from 'pino'; export default pino(); ================================================ FILE: middleware/response-time.js ================================================ /** * Return header with response time * * @param {Object} ctx Koa context * @param {Function} next Next middleware * @returns {Promise} */ export default async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set('spacex-api-response-time', `${ms}ms`); }; ================================================ FILE: models/capsules.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const capsuleSchema = new mongoose.Schema({ serial: { type: String, required: true, unique: true, }, status: { type: String, enum: ['unknown', 'active', 'retired', 'destroyed'], required: true, }, type: { type: String, enum: ['Dragon 1.0', 'Dragon 1.1', 'Dragon 2.0'], required: true, }, dragon: { type: mongoose.ObjectId, ref: 'Dragon', }, reuse_count: { type: Number, default: 0, }, water_landings: { type: Number, default: 0, }, land_landings: { type: Number, default: 0, }, last_update: { type: String, default: null, }, launches: [{ type: mongoose.ObjectId, ref: 'Launch', }], }, { autoCreate: true }); const index = { serial: 'text', last_update: 'text', }; capsuleSchema.index(index); capsuleSchema.plugin(mongoosePaginate); capsuleSchema.plugin(idPlugin); const Capsule = mongoose.model('Capsule', capsuleSchema); export default Capsule; ================================================ FILE: models/company.js ================================================ import mongoose from 'mongoose'; import idPlugin from 'mongoose-id'; const companySchema = new mongoose.Schema({ name: { type: String, }, founder: { type: String, }, founded: { type: Number, }, employees: { type: Number, }, vehicles: { type: Number, }, launch_sites: { type: Number, }, test_sites: { type: Number, }, ceo: { type: String, }, cto: { type: String, }, coo: { type: String, }, cto_propulsion: { type: String, }, valuation: { type: Number, }, headquarters: { address: { type: String, }, city: { type: String, }, state: { type: String, }, }, links: { website: { type: String, }, flickr: { type: String, }, twitter: { type: String, }, elon_twitter: { type: String, }, }, summary: { type: String, }, }, { autoCreate: true }); companySchema.plugin(idPlugin); const Company = mongoose.model('Company', companySchema); export default Company; ================================================ FILE: models/cores.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const coreSchema = new mongoose.Schema({ serial: { type: String, unique: true, required: true, }, block: { type: Number, default: null, }, status: { type: String, enum: ['active', 'inactive', 'unknown', 'expended', 'lost', 'retired'], required: true, }, reuse_count: { type: Number, default: 0, }, rtls_attempts: { type: Number, default: 0, }, rtls_landings: { type: Number, default: 0, }, asds_attempts: { type: Number, default: 0, }, asds_landings: { type: Number, default: 0, }, last_update: { type: String, default: null, }, launches: [{ type: mongoose.ObjectId, ref: 'Launch', }], }, { autoCreate: true }); const index = { serial: 'text', last_update: 'text', }; coreSchema.index(index); coreSchema.plugin(mongoosePaginate); coreSchema.plugin(idPlugin); const Core = mongoose.model('Core', coreSchema); export default Core; ================================================ FILE: models/crew.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const crewSchema = new mongoose.Schema({ name: { type: String, default: null, }, status: { type: String, required: true, enum: ['active', 'inactive', 'retired', 'unknown'], }, agency: { type: String, default: null, }, image: { type: String, default: null, }, wikipedia: { type: String, default: null, }, launches: [{ type: mongoose.ObjectId, ref: 'Launch', }], }, { autoCreate: true }); const index = { name: 'text', }; crewSchema.index(index); crewSchema.plugin(mongoosePaginate); crewSchema.plugin(idPlugin); const Crew = mongoose.model('Crew', crewSchema); export default Crew; ================================================ FILE: models/dragons.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const dragonSchema = new mongoose.Schema({ name: { type: String, unique: true, required: true, }, type: { type: String, required: true, }, active: { type: Boolean, required: true, }, crew_capacity: { type: Number, required: true, }, sidewall_angle_deg: { type: Number, required: true, }, orbit_duration_yr: { type: Number, required: true, }, dry_mass_kg: { type: Number, required: true, }, dry_mass_lb: { type: Number, required: true, }, first_flight: { type: String, default: null, }, heat_shield: { material: { type: String, required: true, }, size_meters: { type: Number, required: true, }, temp_degrees: { type: Number, }, dev_partner: { type: String, }, }, thrusters: { type: mongoose.Mixed, }, launch_payload_mass: { kg: { type: Number, }, lb: { type: Number, }, }, launch_payload_vol: { cubic_meters: { type: Number, }, cubic_feet: { type: Number, }, }, return_payload_mass: { kg: { type: Number, }, lb: { type: Number, }, }, return_payload_vol: { cubic_meters: { type: Number, }, cubic_feet: { type: Number, }, }, pressurized_capsule: { payload_volume: { cubic_meters: { type: Number, }, cubic_feet: { type: Number, }, }, }, trunk: { trunk_volume: { cubic_meters: { type: Number, }, cubic_feet: { type: Number, }, }, cargo: { solar_array: { type: Number, }, unpressurized_cargo: { type: Boolean, }, }, }, height_w_trunk: { meters: { type: Number, }, feet: { type: Number, }, }, diameter: { meters: { type: Number, }, feet: { type: Number, }, }, flickr_images: { type: [ String, ], }, wikipedia: { type: String, }, description: { type: String, }, }, { autoCreate: true }); const index = { name: 'text', }; dragonSchema.index(index); dragonSchema.plugin(mongoosePaginate); dragonSchema.plugin(idPlugin); const Dragon = mongoose.model('Dragon', dragonSchema); export default Dragon; ================================================ FILE: models/history.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const historySchema = new mongoose.Schema({ title: { type: String, default: null, }, event_date_utc: { type: String, default: null, }, event_date_unix: { type: Number, default: null, }, details: { type: String, default: null, }, links: { article: { type: String, default: null, }, }, }, { autoCreate: true }); const index = { title: 'text', details: 'text', }; historySchema.index(index); historySchema.plugin(mongoosePaginate); historySchema.plugin(idPlugin); const History = mongoose.model('History', historySchema); export default History; ================================================ FILE: models/index.js ================================================ export { default as Capsule } from './capsules.js'; export { default as Company } from './company.js'; export { default as Core } from './cores.js'; export { default as Crew } from './crew.js'; export { default as Dragon } from './dragons.js'; export { default as History } from './history.js'; export { default as Landpad } from './landpads.js'; export { default as Launch } from './launches.js'; export { default as Launchpad } from './launchpads.js'; export { default as Payload } from './payloads.js'; export { default as Roadster } from './roadster.js'; export { default as Rocket } from './rockets.js'; export { default as Ship } from './ships.js'; export { default as Starlink } from './starlink.js'; export { default as User } from './users.js'; ================================================ FILE: models/landpads.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const landpadSchema = new mongoose.Schema({ name: { type: String, default: null, }, full_name: { type: String, default: null, }, status: { type: String, enum: ['active', 'inactive', 'unknown', 'retired', 'lost', 'under construction'], required: true, }, type: { type: String, default: null, }, locality: { type: String, default: null, }, region: { type: String, default: null, }, latitude: { type: Number, default: null, }, longitude: { type: Number, default: null, }, landing_attempts: { type: Number, default: 0, }, landing_successes: { type: Number, default: 0, }, wikipedia: { type: String, default: null, }, details: { type: String, default: null, }, launches: [{ type: mongoose.ObjectId, ref: 'Launch', }], images: { large: [String], }, }, { autoCreate: true }); const index = { name: 'text', full_name: 'text', details: 'text', }; landpadSchema.index(index); landpadSchema.plugin(mongoosePaginate); landpadSchema.plugin(idPlugin); const Landpad = mongoose.model('Landpad', landpadSchema); export default Landpad; ================================================ FILE: models/launches.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const launchSchema = new mongoose.Schema({ flight_number: { type: Number, required: true, }, name: { type: String, unique: true, required: true, }, date_utc: { type: String, required: true, }, date_unix: { type: Number, required: true, }, date_local: { type: String, required: true, }, date_precision: { type: String, required: true, enum: ['half', 'quarter', 'year', 'month', 'day', 'hour'], }, static_fire_date_utc: { type: String, default: null, }, static_fire_date_unix: { type: Number, default: null, }, tbd: { type: Boolean, default: false, }, net: { type: Boolean, default: false, }, window: { type: Number, default: null, }, rocket: { type: mongoose.ObjectId, ref: 'Rocket', default: null, }, success: { type: Boolean, default: null, }, failures: [ { _id: false, time: { type: Number, }, altitude: { type: Number, }, reason: { type: String, }, }, ], upcoming: { type: Boolean, required: true, }, details: { type: String, default: null, }, fairings: { reused: { type: Boolean, default: null, }, recovery_attempt: { type: Boolean, default: null, }, recovered: { type: Boolean, default: null, }, ships: [{ type: mongoose.ObjectId, ref: 'Ship', }], }, crew: [{ _id: false, crew: { type: mongoose.ObjectId, ref: 'Crew', default: null, }, role: { type: String, default: null, }, }], ships: [{ type: mongoose.ObjectId, ref: 'Ship', }], capsules: [{ type: mongoose.ObjectId, ref: 'Capsule', }], payloads: [{ type: mongoose.ObjectId, ref: 'Payload', }], launchpad: { type: mongoose.ObjectId, ref: 'Launchpad', default: null, }, cores: [{ _id: false, core: { type: mongoose.ObjectId, ref: 'Core', default: null, }, flight: { type: Number, default: null, }, gridfins: { type: Boolean, default: null, }, legs: { type: Boolean, default: null, }, reused: { type: Boolean, default: null, }, landing_attempt: { type: Boolean, default: null, }, landing_success: { type: Boolean, default: null, }, landing_type: { type: String, default: null, }, landpad: { type: mongoose.ObjectId, ref: 'Landpad', default: null, }, }], links: { patch: { small: { type: String, default: null, }, large: { type: String, default: null, }, }, reddit: { campaign: { type: String, default: null, }, launch: { type: String, default: null, }, media: { type: String, default: null, }, recovery: { type: String, default: null, }, }, flickr: { small: [String], original: [String], }, presskit: { type: String, default: null, }, webcast: { type: String, default: null, }, youtube_id: { type: String, default: null, }, article: { type: String, default: null, }, wikipedia: { type: String, default: null, }, }, auto_update: { type: Boolean, default: true, }, launch_library_id: { type: String, default: null, }, }, { autoCreate: true }); const index = { name: 'text', details: 'text', }; launchSchema.index(index); launchSchema.plugin(mongoosePaginate); launchSchema.plugin(idPlugin); const Launch = mongoose.model('Launch', launchSchema); export default Launch; ================================================ FILE: models/launchpads.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const launchpadSchema = new mongoose.Schema({ name: { type: String, default: null, }, full_name: { type: String, default: null, }, status: { type: String, enum: ['active', 'inactive', 'unknown', 'retired', 'lost', 'under construction'], required: true, }, locality: { type: String, default: null, }, region: { type: String, default: null, }, timezone: { type: String, default: null, }, latitude: { type: Number, default: null, }, longitude: { type: Number, default: null, }, launch_attempts: { type: Number, default: 0, }, launch_successes: { type: Number, default: 0, }, rockets: [{ type: mongoose.ObjectId, ref: 'Rocket', }], launches: [{ type: mongoose.ObjectId, ref: 'Launch', }], details: { type: String, default: null, }, images: { large: [String], }, }, { autoCreate: true }); const index = { name: 'text', full_name: 'text', details: 'text', }; launchpadSchema.index(index); launchpadSchema.plugin(mongoosePaginate); launchpadSchema.plugin(idPlugin); const Launchpad = mongoose.model('Launchpad', launchpadSchema); export default Launchpad; ================================================ FILE: models/payloads.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const payloadSchema = new mongoose.Schema({ name: { type: String, default: null, unique: true, }, type: { type: String, default: null, }, reused: { type: Boolean, default: false, }, launch: { type: mongoose.ObjectId, ref: 'Launch', default: null, }, customers: [String], norad_ids: [Number], nationalities: [String], manufacturers: [String], mass_kg: { type: Number, default: null, }, mass_lbs: { type: Number, default: null, }, orbit: { type: String, default: null, }, reference_system: { type: String, default: null, }, regime: { type: String, default: null, }, longitude: { type: Number, default: null, }, semi_major_axis_km: { type: Number, default: null, }, eccentricity: { type: Number, default: null, }, periapsis_km: { type: Number, default: null, }, apoapsis_km: { type: Number, default: null, }, inclination_deg: { type: Number, default: null, }, period_min: { type: Number, default: null, }, lifespan_years: { type: Number, default: null, }, epoch: { type: String, default: null, }, mean_motion: { type: Number, default: null, }, raan: { type: Number, default: null, }, arg_of_pericenter: { type: Number, default: null, }, mean_anomaly: { type: Number, default: null, }, dragon: { capsule: { type: mongoose.ObjectId, ref: 'Capsule', default: null, }, mass_returned_kg: { type: Number, default: null, }, mass_returned_lbs: { type: Number, default: null, }, flight_time_sec: { type: Number, default: null, }, manifest: { type: String, default: null, }, water_landing: { type: Boolean, default: null, }, land_landing: { type: Boolean, default: null, }, }, }, { autoCreate: true }); const index = { name: 'text', }; payloadSchema.index(index); payloadSchema.plugin(mongoosePaginate); payloadSchema.plugin(idPlugin); const Payload = mongoose.model('Payload', payloadSchema); export default Payload; ================================================ FILE: models/roadster.js ================================================ import mongoose from 'mongoose'; import idPlugin from 'mongoose-id'; const roadsterSchema = new mongoose.Schema({ name: { type: String, }, launch_date_utc: { type: String, }, launch_date_unix: { type: Number, }, launch_mass_kg: { type: Number, }, launch_mass_lbs: { type: Number, }, norad_id: { type: Number, }, epoch_jd: { type: Number, }, orbit_type: { type: String, }, apoapsis_au: { type: Number, }, periapsis_au: { type: Number, }, semi_major_axis_au: { type: Number, }, eccentricity: { type: Number, }, inclination: { type: Number, }, longitude: { type: Number, }, periapsis_arg: { type: Number, }, period_days: { type: Number, }, speed_kph: { type: Number, }, speed_mph: { type: Number, }, earth_distance_km: { type: Number, }, earth_distance_mi: { type: Number, }, mars_distance_km: { type: Number, }, mars_distance_mi: { type: Number, }, flickr_images: [String], wikipedia: { type: String, }, video: { type: String, }, details: { type: String, }, }, { autoCreate: true }); roadsterSchema.plugin(idPlugin); const Roadster = mongoose.model('Roadster', roadsterSchema); export default Roadster; ================================================ FILE: models/rockets.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const rocketSchema = new mongoose.Schema({ name: { type: String, }, type: { type: String, }, active: { type: Boolean, }, stages: { type: Number, }, boosters: { type: Number, }, cost_per_launch: { type: Number, }, success_rate_pct: { type: Number, }, first_flight: { type: String, }, country: { type: String, }, company: { type: String, }, height: { meters: { type: Number, }, feet: { type: Number, }, }, diameter: { meters: { type: Number, }, feet: { type: Number, }, }, mass: { kg: { type: Number, }, lb: { type: Number, }, }, payload_weights: { type: [ mongoose.Mixed, ], }, first_stage: { reusable: { type: Boolean, }, engines: { type: Number, }, fuel_amount_tons: { type: Number, }, burn_time_sec: { type: Number, }, thrust_sea_level: { kN: { type: Number, }, lbf: { type: Number, }, }, thrust_vacuum: { kN: { type: Number, }, lbf: { type: Number, }, }, }, second_stage: { reusable: { type: Boolean, }, engines: { type: Number, }, fuel_amount_tons: { type: Number, }, burn_time_sec: { type: Number, }, thrust: { kN: { type: Number, }, lbf: { type: Number, }, }, payloads: { option_1: { type: String, }, composite_fairing: { height: { meters: { type: Number, }, feet: { type: Number, }, }, diameter: { meters: { type: Number, }, feet: { type: Number, }, }, }, }, }, engines: { number: { type: Number, }, type: { type: String, }, version: { type: String, }, layout: { type: String, }, isp: { sea_level: { type: Number, }, vacuum: { type: Number, }, }, engine_loss_max: { type: Number, }, propellant_1: { type: String, }, propellant_2: { type: String, }, thrust_sea_level: { kN: { type: Number, }, lbf: { type: Number, }, }, thrust_vacuum: { kN: { type: Number, }, lbf: { type: Number, }, }, thrust_to_weight: { type: Number, }, }, landing_legs: { number: { type: Number, }, material: { type: mongoose.Mixed, }, }, flickr_images: { type: [ String, ], }, wikipedia: { type: String, }, description: { type: String, }, }, { autoCreate: true }); const index = { name: 'text', }; rocketSchema.index(index); rocketSchema.plugin(mongoosePaginate); rocketSchema.plugin(idPlugin); const Rocket = mongoose.model('Rocket', rocketSchema); export default Rocket; ================================================ FILE: models/ships.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const shipSchema = new mongoose.Schema({ name: { type: String, unique: true, required: true, }, legacy_id: { type: String, default: null, }, model: { type: String, default: null, }, type: { type: String, default: null, }, roles: [ String, ], active: { type: Boolean, required: true, }, imo: { type: Number, default: null, }, mmsi: { type: Number, default: null, }, abs: { type: Number, default: null, }, class: { type: Number, default: null, }, mass_kg: { type: Number, default: null, }, mass_lbs: { type: Number, default: null, }, year_built: { type: Number, default: null, }, home_port: { type: String, default: null, }, status: { type: String, default: null, }, speed_kn: { type: Number, default: null, }, course_deg: { type: Number, default: null, }, latitude: { type: Number, default: null, }, longitude: { type: Number, default: null, }, last_ais_update: { type: String, default: null, }, link: { type: String, default: null, }, image: { type: String, default: null, }, launches: [{ type: mongoose.ObjectId, ref: 'Launch', }], }, { autoCreate: true }); const index = { name: 'text', }; shipSchema.index(index); shipSchema.plugin(mongoosePaginate); shipSchema.plugin(idPlugin); const Ship = mongoose.model('Ship', shipSchema); export default Ship; ================================================ FILE: models/starlink.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const starlinkSchema = new mongoose.Schema({ version: { type: String, default: null, }, launch: { type: mongoose.ObjectId, ref: 'Launch', default: null, }, longitude: { type: Number, default: null, }, latitude: { type: Number, default: null, }, height_km: { type: Number, default: null, }, velocity_kms: { type: Number, default: null, }, spaceTrack: { CCSDS_OMM_VERS: { type: String, default: null, }, COMMENT: { type: String, default: null, }, CREATION_DATE: { type: String, default: null, }, ORIGINATOR: { type: String, default: null, }, OBJECT_NAME: { type: String, default: null, }, OBJECT_ID: { type: String, default: null, }, CENTER_NAME: { type: String, default: null, }, REF_FRAME: { type: String, default: null, }, TIME_SYSTEM: { type: String, default: null, }, MEAN_ELEMENT_THEORY: { type: String, default: null, }, EPOCH: { type: String, default: null, }, MEAN_MOTION: { type: Number, default: null, }, ECCENTRICITY: { type: Number, default: null, }, INCLINATION: { type: Number, default: null, }, RA_OF_ASC_NODE: { type: Number, default: null, }, ARG_OF_PERICENTER: { type: Number, default: null, }, MEAN_ANOMALY: { type: Number, default: null, }, EPHEMERIS_TYPE: { type: Number, default: null, }, CLASSIFICATION_TYPE: { type: String, default: null, }, NORAD_CAT_ID: { type: Number, default: null, }, ELEMENT_SET_NO: { type: Number, default: null, }, REV_AT_EPOCH: { type: Number, default: null, }, BSTAR: { type: Number, default: null, }, MEAN_MOTION_DOT: { type: Number, default: null, }, MEAN_MOTION_DDOT: { type: Number, default: null, }, SEMIMAJOR_AXIS: { type: Number, default: null, }, PERIOD: { type: Number, default: null, }, APOAPSIS: { type: Number, default: null, }, PERIAPSIS: { type: Number, default: null, }, OBJECT_TYPE: { type: String, default: null, }, RCS_SIZE: { type: String, default: null, }, COUNTRY_CODE: { type: String, default: null, }, LAUNCH_DATE: { type: String, default: null, }, SITE: { type: String, default: null, }, DECAY_DATE: { type: String, default: null, }, DECAYED: { type: Number, default: null, }, FILE: { type: Number, default: null, }, GP_ID: { type: Number, default: null, }, TLE_LINE0: { type: String, default: null, }, TLE_LINE1: { type: String, default: null, }, TLE_LINE2: { type: String, default: null, }, }, }, { autoCreate: true }); starlinkSchema.plugin(mongoosePaginate); starlinkSchema.plugin(idPlugin); const Starlink = mongoose.model('Starlink', starlinkSchema); export default Starlink; ================================================ FILE: models/users.js ================================================ import mongoose from 'mongoose'; import mongoosePaginate from 'mongoose-paginate-v2'; import idPlugin from 'mongoose-id'; const userSchema = new mongoose.Schema({ name: { type: String, default: null, }, key: { type: String, required: true, }, role: { type: String, required: true, enum: ['superuser', 'user', 'create', 'update', 'delete'], }, }, { autoCreate: true }); userSchema.plugin(mongoosePaginate); userSchema.plugin(idPlugin); const User = mongoose.model('User', userSchema); export default User; ================================================ FILE: package.json ================================================ { "name": "spacex-api", "version": "4.0.0", "description": "Open Source REST API for data about SpaceX", "main": "server.js", "type": "module", "scripts": { "test": "npm run lint && npm run check-dependencies && jest --silent --verbose", "start": "node server.js", "worker": "node jobs/worker.js", "lint": "eslint .", "check-dependencies": "npx depcheck --ignores=\"pino-pretty\"" }, "repository": { "type": "git", "url": "git+https://github.com/r-spacex/SpaceX-API" }, "keywords": [ "spacex", "space", "rocket", "rest-api", "mongodb", "koa" ], "author": "Jake Meyer ", "license": "Apache-2.0", "bugs": { "url": "https://github.com/r-spacex/SpaceX-API/issues" }, "homepage": "https://github.com/r-spacex/SpaceX-API", "dependencies": { "blake3": "^2.1.7", "cheerio": "^1.0.0-rc.12", "cron": "^2.1.0", "dotenv": "^16.0.1", "fuzzball": "^2.1.2", "got": "^12.3.1", "ioredis": "^5.2.3", "koa": "^2.13.4", "koa-bodyparser": "^4.3.0", "koa-conditional-get": "^3.0.0", "koa-etag": "^4.0.0", "koa-helmet": "^6.1.0", "koa-router": "^12.0.0", "koa2-cors": "^2.0.6", "lodash": "^4.17.21", "moment-range": "^4.0.2", "moment-timezone": "^0.5.37", "mongoose": "^6.5.3", "mongoose-id": "^0.1.3", "mongoose-paginate-v2": "^1.7.0", "pino": "^8.4.2", "rss-parser": "^3.12.0", "tle.js": "^4.7.1", "tough-cookie": "^4.1.2" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.35.1", "@typescript-eslint/parser": "^5.35.1", "eslint": "^8.23.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jest": "^26.8.7", "jest": "^29.0.1" }, "engines": { "node": ">=14.16" }, "jest": { "testEnvironment": "node" }, "volta": { "node": "18.7.0" } } ================================================ FILE: routes/admin/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/admin/v4/index.js ================================================ import Router from 'koa-router'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/admin', }); // Clear redis cache router.delete('/cache', auth, authz('cache:clear'), async (ctx) => { try { await cache.redis.flushall(); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Healthcheck router.get('/health', async (ctx) => { ctx.status = 200; }); export default router; ================================================ FILE: routes/capsules/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/capsules/v4/index.js ================================================ import Router from 'koa-router'; import { Capsule } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/capsules', }); // Get all capsules router.get('/', cache(300), async (ctx) => { try { const result = await Capsule.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one capsule router.get('/:id', cache(300), async (ctx) => { const result = await Capsule.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query capsules router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Capsule.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create capsule router.post('/', auth, authz('capsule:create'), async (ctx) => { try { const capsule = new Capsule(ctx.request.body); await capsule.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update capsule router.patch('/:id', auth, authz('capsule:update'), async (ctx) => { try { await Capsule.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete capsule router.delete('/:id', auth, authz('capsule:delete'), async (ctx) => { try { await Capsule.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/company/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/company/v4/index.js ================================================ import Router from 'koa-router'; import { Company } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/company', }); // Get company info router.get('/', cache(86400), async (ctx) => { try { const result = await Company.findOne({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Update company info router.patch('/:id', auth, authz('company:update'), async (ctx) => { try { await Company.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/cores/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/cores/v4/index.js ================================================ import Router from 'koa-router'; import { Core } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/cores', }); // Get all cores router.get('/', cache(300), async (ctx) => { try { const result = await Core.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one core router.get('/:id', cache(300), async (ctx) => { const result = await Core.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query cores router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Core.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create core router.post('/', auth, authz('core:create'), async (ctx) => { try { const core = new Core(ctx.request.body); await core.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update core router.patch('/:id', auth, authz('core:update'), async (ctx) => { try { await Core.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete core router.delete('/:id', auth, authz('core:delete'), async (ctx) => { try { await Core.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/crew/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/crew/v4/index.js ================================================ import Router from 'koa-router'; import { Crew } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/crew', }); // Get all crew router.get('/', cache(300), async (ctx) => { try { const result = await Crew.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one crew member router.get('/:id', cache(300), async (ctx) => { const result = await Crew.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query crew members router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Crew.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create crew member router.post('/', auth, authz('crew:create'), async (ctx) => { try { const crew = new Crew(ctx.request.body); await crew.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update crew member router.patch('/:id', auth, authz('crew:update'), async (ctx) => { try { await Crew.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete crew member router.delete('/:id', auth, authz('crew:delete'), async (ctx) => { try { await Crew.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/dragons/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/dragons/v4/index.js ================================================ import Router from 'koa-router'; import { Dragon } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/dragons', }); // Get all dragons router.get('/', cache(86400), async (ctx) => { try { const result = await Dragon.find({}, null, { sort: { name: 'asc' } }); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one dragon router.get('/:id', cache(86400), async (ctx) => { const result = await Dragon.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query dragons router.post('/query', cache(86400), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Dragon.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a dragon router.post('/', auth, authz('dragon:create'), async (ctx) => { try { const dragon = new Dragon(ctx.request.body); await dragon.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a dragon router.patch('/:id', auth, authz('dragon:update'), async (ctx) => { try { await Dragon.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete dragon router.delete('/:id', auth, authz('dragon:delete'), async (ctx) => { try { await Dragon.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/history/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/history/v4/index.js ================================================ import Router from 'koa-router'; import { History } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/history', }); // Get all history events router.get('/', cache(300), async (ctx) => { try { const result = await History.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one history event router.get('/:id', cache(300), async (ctx) => { const result = await History.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query history events router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await History.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a history event router.post('/', auth, authz('history:create'), async (ctx) => { try { const history = new History(ctx.request.body); await history.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a history event router.patch('/:id', auth, authz('history:update'), async (ctx) => { try { await History.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete history event router.delete('/:id', auth, authz('history:delete'), async (ctx) => { try { await History.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/index.js ================================================ /* eslint-disable no-restricted-syntax */ import Router from 'koa-router'; const FOLDERS = await Promise.all([ import('./admin/index.js'), import('./capsules/index.js'), import('./company/index.js'), import('./cores/index.js'), import('./crew/index.js'), import('./dragons/index.js'), import('./history/index.js'), import('./landpads/index.js'), import('./launches/index.js'), import('./launchpads/index.js'), import('./payloads/index.js'), import('./roadster/index.js'), import('./rockets/index.js'), import('./ships/index.js'), import('./starlink/index.js'), import('./users/index.js'), ]); const ROUTER = new Router(); // Register all routes + all versions export default async () => { for await (const routeFolder of FOLDERS) { if (routeFolder?.default) { for await (const version of routeFolder.default) { ROUTER.use(version?.default?.routes()); } } } return ROUTER.routes(); }; ================================================ FILE: routes/landpads/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/landpads/v4/index.js ================================================ import Router from 'koa-router'; import { Landpad } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/landpads', }); // Get all landpads router.get('/', cache(300), async (ctx) => { try { const result = await Landpad.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one landpad router.get('/:id', cache(300), async (ctx) => { const result = await Landpad.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query landpads router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Landpad.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a landpad router.post('/', auth, authz('landpad:create'), async (ctx) => { try { const landpad = new Landpad(ctx.request.body); await landpad.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a landpad router.patch('/:id', auth, authz('landpad:update'), async (ctx) => { try { await Landpad.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete landpad router.delete('/:id', auth, authz('landpad:delete'), async (ctx) => { try { await Landpad.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/launches/index.js ================================================ export default [ import('./v4/index.js'), import('./v5/index.js'), ]; ================================================ FILE: routes/launches/v4/_transform-query.js ================================================ import _ from 'lodash'; /** * Transform V4 query into V5 query * * @param {Object} ctx Koa context * @param {function} next Koa next function * @returns {void} */ export default async (query) => { const transformed = query; if (query?.options?.populate) { if (Array.isArray(query.options.populate)) { query.options.populate.forEach((item, index) => { if (_.isObject(item)) { // Index is not user specified transformed.options.populate[index].path = transformed.options.populate?.[index]?.path?.replace('crew', 'crew.crew'); } }); } if (_.isObject(query.options.populate) && !Array.isArray(query.options.populate)) { transformed.options.populate.path = transformed.options.populate.path?.replace('crew', 'crew.crew'); } if (_.isString(query.options.populate)) { transformed.options.populate = transformed.options.populate?.replace('crew', 'crew.crew'); } } return transformed; }; ================================================ FILE: routes/launches/v4/_transform-response.js ================================================ /* eslint-disable no-underscore-dangle */ const buildCrew = (launch) => { if (Array.isArray(launch?.crew) && launch.crew.length === 0) { return []; } return launch.crew.map((crew) => { if (crew?.crew) { return crew?.crew; } return crew; }); }; export default async (payload) => { if (Array.isArray(payload)) { return payload.map((launch) => { if (Array.isArray(launch?.crew)) { return { ...launch.toObject(), crew: buildCrew(launch.toObject()), }; } return { ...launch.toObject(), }; }); } if (Array.isArray(payload?.docs)) { const docs = payload.docs.map((launch) => { if (Array.isArray(launch?.crew)) { return { ...launch.toObject(), crew: buildCrew(launch.toObject()), }; } return { ...launch.toObject(), }; }); return { ...payload, docs, }; } return { ...payload.toObject(), crew: buildCrew(payload.toObject()), }; }; ================================================ FILE: routes/launches/v4/index.js ================================================ import Router from 'koa-router'; import { Launch } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; import transformResponse from './_transform-response.js'; import transformQuery from './_transform-query.js'; const router = new Router({ prefix: '/v4/launches', }); // // Convenience Endpoints // // Get past launches router.get('/past', cache(20), async (ctx) => { try { const result = await Launch.find({ upcoming: false, }, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = await transformResponse(result); } catch (error) { ctx.throw(400, error.message); } }); // Get upcoming launches router.get('/upcoming', cache(20), async (ctx) => { try { const result = await Launch.find({ upcoming: true, }, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = await transformResponse(result); } catch (error) { ctx.throw(400, error.message); } }); // Get latest launch router.get('/latest', cache(20), async (ctx) => { try { const result = await Launch.findOne({ upcoming: false, }, null, { sort: { flight_number: 'desc', }, }); ctx.status = 200; ctx.body = await transformResponse(result); } catch (error) { ctx.throw(400, error.message); } }); // Get next launch router.get('/next', cache(20), async (ctx) => { try { const result = await Launch.findOne({ upcoming: true, }, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = await transformResponse(result); } catch (error) { ctx.throw(400, error.message); } }); // // Standard Endpoints // // Get all launches router.get('/', cache(20), async (ctx) => { try { const result = await Launch.find({}, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = await transformResponse(result); } catch (error) { ctx.throw(400, error.message); } }); // Get one launch router.get('/:id', cache(20), async (ctx) => { const result = await Launch.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = await transformResponse(result); }); // Query launches router.post('/query', cache(20), async (ctx) => { const { query = {}, options = {} } = await transformQuery(ctx.request.body); try { const result = await Launch.paginate(query, options); ctx.status = 200; ctx.body = await transformResponse(result); } catch (error) { ctx.throw(400, error.message); } }); // Create a launch router.post('/', auth, authz('launch:create'), async (ctx) => { try { const launch = new Launch(ctx.request.body); await launch.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a launch router.patch('/:id', auth, authz('launch:update'), async (ctx) => { try { await Launch.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true, }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a launch router.delete('/:id', auth, authz('launch:delete'), async (ctx) => { try { await Launch.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/launches/v5/index.js ================================================ import Router from 'koa-router'; import { Launch } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v5|latest)/launches', }); // // Convenience Endpoints // // Get past launches router.get('/past', cache(20), async (ctx) => { try { const result = await Launch.find({ upcoming: false, }, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get upcoming launches router.get('/upcoming', cache(20), async (ctx) => { try { const result = await Launch.find({ upcoming: true, }, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get latest launch router.get('/latest', cache(20), async (ctx) => { try { const result = await Launch.findOne({ upcoming: false, }, null, { sort: { flight_number: 'desc', }, }); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get next launch router.get('/next', cache(20), async (ctx) => { try { const result = await Launch.findOne({ upcoming: true, }, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // // Standard Endpoints // // Get all launches router.get('/', cache(20), async (ctx) => { try { const result = await Launch.find({}, null, { sort: { flight_number: 'asc', }, }); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one launch router.get('/:id', cache(20), async (ctx) => { const result = await Launch.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query launches router.post('/query', cache(20), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Launch.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a launch router.post('/', auth, authz('launch:create'), async (ctx) => { try { const launch = new Launch(ctx.request.body); await launch.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a launch router.patch('/:id', auth, authz('launch:update'), async (ctx) => { try { await Launch.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true, }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a launch router.delete('/:id', auth, authz('launch:delete'), async (ctx) => { try { await Launch.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/launchpads/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/launchpads/v4/index.js ================================================ import Router from 'koa-router'; import { Launchpad } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/launchpads', }); // Get all launchpads router.get('/', cache(300), async (ctx) => { try { const result = await Launchpad.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one launchpad router.get('/:id', cache(300), async (ctx) => { const result = await Launchpad.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query launchpads router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Launchpad.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a launchpad router.post('/', auth, authz('launchpad:create'), async (ctx) => { try { const launchpad = new Launchpad(ctx.request.body); await launchpad.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a launchpad router.patch('/:id', auth, authz('launchpad:update'), async (ctx) => { try { await Launchpad.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a launchpad router.delete('/:id', auth, authz('launchpad:delete'), async (ctx) => { try { await Launchpad.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/payloads/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/payloads/v4/index.js ================================================ import Router from 'koa-router'; import { Payload } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/payloads', }); // Get all payloads router.get('/', cache(300), async (ctx) => { try { const result = await Payload.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one payload router.get('/:id', cache(300), async (ctx) => { const result = await Payload.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query payloads router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Payload.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a payload router.post('/', auth, authz('payload:create'), async (ctx) => { try { const payload = new Payload(ctx.request.body); await payload.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a payload router.patch('/:id', auth, authz('payload:update'), async (ctx) => { try { await Payload.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true, }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a payload router.delete('/:id', auth, authz('payload:delete'), async (ctx) => { try { await Payload.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/roadster/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/roadster/v4/index.js ================================================ import Router from 'koa-router'; import { Roadster } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/roadster', }); // Get roadster router.get('/', cache(300), async (ctx) => { try { const result = await Roadster.findOne({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Query roadster router.post('/query', cache(300), async (ctx) => { const { query = {}, options = { select: '' } } = ctx.request.body; try { const result = await Roadster.findOne(query).select(options.select).exec(); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Update roadster router.patch('/:id', auth, authz('roadster:update'), async (ctx) => { try { await Roadster.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/rockets/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/rockets/v4/index.js ================================================ import Router from 'koa-router'; import { Rocket } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/rockets', }); // Get all rockets router.get('/', cache(86400), async (ctx) => { try { const result = await Rocket.find({}, null, { sort: { first_flight: 'asc' } }); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one rocket router.get('/:id', cache(86400), async (ctx) => { const result = await Rocket.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query rocket router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Rocket.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a rocket router.post('/', auth, authz('rocket:create'), async (ctx) => { try { const rocket = new Rocket(ctx.request.body); await rocket.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a rocket router.patch('/:id', auth, authz('rocket:update'), async (ctx) => { try { await Rocket.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a rocket router.delete('/:id', auth, authz('rocket:delete'), async (ctx) => { try { await Rocket.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/ships/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/ships/v4/index.js ================================================ import Router from 'koa-router'; import { Ship } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/ships', }); // Get all ships router.get('/', cache(300), async (ctx) => { try { const result = await Ship.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one ship router.get('/:id', cache(300), async (ctx) => { const result = await Ship.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query ships router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Ship.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a ship router.post('/', auth, authz('ship:create'), async (ctx) => { try { const ship = new Ship(ctx.request.body); await ship.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a ship router.patch('/:id', auth, authz('ship:update'), async (ctx) => { try { await Ship.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a ship router.delete('/:id', auth, authz('ship:delete'), async (ctx) => { try { await Ship.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/starlink/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/starlink/v4/index.js ================================================ import Router from 'koa-router'; import { Starlink } from '../../../models/index.js'; import { auth, authz, cache } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/starlink', }); // Get all Starlink satellites router.get('/', cache(3600), async (ctx) => { try { const result = await Starlink.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one Starlink satellite router.get('/:id', cache(3600), async (ctx) => { const result = await Starlink.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query Starlink satellites router.post('/query', cache(3600), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await Starlink.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a Starlink satellite router.post('/', auth, authz('starlink:create'), async (ctx) => { try { const ship = new Starlink(ctx.request.body); await ship.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a Starlink satellite router.patch('/:norad_id', auth, authz('starlink:update'), async (ctx) => { try { await Starlink.findOneAndUpdate({ 'spaceTrack.NORAD_CAT_ID': ctx.params.norad_id }, ctx.request.body, { runValidators: true, setDefaultsOnInsert: true, upsert: true, }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a Starlink satellite router.delete('/:id', auth, authz('starlink:delete'), async (ctx) => { try { await Starlink.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: routes/users/index.js ================================================ export default [ import('./v4/index.js'), ]; ================================================ FILE: routes/users/v4/index.js ================================================ import Router from 'koa-router'; import { User } from '../../../models/index.js'; import { auth, authz } from '../../../middleware/index.js'; const router = new Router({ prefix: '/(v4|latest)/users', }); // Get all users router.get('/', auth, authz('user:list'), async (ctx) => { try { const result = await User.find({}); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Get one user router.get('/:id', auth, authz('user:one'), async (ctx) => { const result = await User.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query users router.post('/query', auth, authz('user:query'), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await User.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } }); // Create a user router.post('/', auth, authz('user:create'), async (ctx) => { try { const user = new User(ctx.request.body); await user.save(); ctx.status = 201; } catch (error) { ctx.throw(400, error.message); } }); // Update a user router.patch('/:id', auth, authz('user:update'), async (ctx) => { try { await User.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true, }); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Delete a user router.delete('/:id', auth, authz('user:delete'), async (ctx) => { try { await User.findByIdAndDelete(ctx.params.id); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); export default router; ================================================ FILE: server.js ================================================ import http from 'http'; import mongoose from 'mongoose'; import logger from './middleware/logger.js'; import { DEFAULT_PORT } from './lib/constants.js'; import app from './app.js'; const PORT = process.env.PORT ?? DEFAULT_PORT; const SERVER = http.createServer(app.callback()); // Gracefully close Mongo connection const gracefulShutdown = (msg) => { logger.info(`Shutdown initiated: ${msg}`); mongoose.connection.close(false, () => { logger.info('Mongo closed'); SERVER.close(() => { logger.info('Shutting down...'); process.exit(); }); }); }; // Server start SERVER.listen(PORT, '0.0.0.0', () => { logger.info(`Running on port: ${PORT}`); // Handle kill commands process.on('SIGTERM', gracefulShutdown); // Handle interrupts process.on('SIGINT', gracefulShutdown); // Prevent dirty exit on uncaught exceptions: process.on('uncaughtException', gracefulShutdown); // Prevent dirty exit on unhandled promise rejection process.on('unhandledRejection', gracefulShutdown); }); ================================================ FILE: start.sh ================================================ #!/usr/bin/env sh if [ "$SPACEX_WORKER" = "true" ]; then node ./jobs/worker.js else node ./server.js fi ================================================ FILE: tests/index.test.js ================================================ it('should expect true to be true', () => { expect(true).toBe(true); });