AstroWay/api v2.295.7 · ja
すべてのシステムが正常です

アストロロジーアプリの構築方法: 開発者向けの完全ガイド

アストロロジーアプリの作成方法 - APIの選択、設計、最初のナタールチャート、シナストリーとトランジットの追加、デプロイ

🕓 2026-04-14 の記録、2026-05-09 更新。 TS / Python / PHP SDK は public registries で公開中 - @astroway/sdk (npm)、astroway (PyPI)、astroway/sdk (Packagist)。詳細は changelog を参照。

占星術アプリを作りたい。どこから始める?このガイドでは、API の選定からプロダクトのデプロイまで、判断ポイントとコードをすべて紹介する。

コードを書く前に決めよう:計算はクライアント側か、サーバー側か?

クライアント(例:ブラウザ上の Swiss Ephemeris WASM):

  • メリット:リクエストごとのコストなし、オフラインで動作、完全なコントロール
  • デメリット:ユーザーごとに約 2 MB の WASM、複雑なライセンス(Swiss Ephemeris の商用利用には制約がある)、計算コードのメンテナンスは自分持ち

サーバーサイド API(AstroWay、Prokerala など):

  • メリット:クライアント側の依存なし、アプリの bundle が小さい、モバイルで高速、ライセンスの悩みなし
  • デメリット:リクエストごとのクレジット消費、「コールド」リクエストでのレイテンシ

ほとんどのアプリではサーバーサイド API が勝つ。モバイルユーザーは 2 MB の WASM を待ってくれないし、ライセンスの細かい問題に関わる価値はない。それに、最新の API は同一リクエストを無料でキャッシュする(AstroWay は X-Cache: HIT ヘッダー付きの 5 分キャッシュを提供)。

重要なポイント:

  • 精度:内部で Swiss Ephemeris を使っているものを探そう(±1 秒角)。API が明言していなければ、問い合わせてみよう。
  • カバレッジ:ネイタル + シナストリー + トランジットが最低ライン。プログレッションやリターンは次のレベル。レアな技法(レクティフィケーション、Human Design)は差別化要素。
  • SDK の品質:型付きの TypeScript / Python SDK は何時間も節約してくれる。
  • 料金モデル:credit-based は per-request よりも混合負荷でスケールしやすい。

このチュートリアルでは AstroWay API を使う。4 つすべてを満たすからだ(777 エンドポイント、Swiss Ephemeris、型付き SDK、credit-based)。

ステップ 1:バックエンドを立てる

Section titled “ステップ 1:バックエンドを立てる”

ブラウザから占星術 API を直接叩いてはいけない - API キーが漏洩する。必ず自分のバックエンド経由にしよう。

Terminal window
mkdir my-astrology-app
cd my-astrology-app
npm init -y
npm install express @astroway/sdk zod
npm install -D typescript @types/express @types/node tsx

src/server.ts を作成:

import express from 'express';
import { Astroway } from '@astroway/sdk';
import { z } from 'zod';
const app = express();
app.use(express.json());
const client = new Astroway({
apiKey: process.env.ASTROWAY_API_KEY!,
});
const BirthInput = z.object({
date: z.string(),
time: z.string(),
timezoneOffset: z.number(),
latitude: z.number(),
longitude: z.number(),
});
app.post('/api/chart', async (req, res) => {
const parsed = BirthInput.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: parsed.error });
try {
const chart = await client.chart.compute({
...parsed.data,
houseSystem: 'P',
});
res.json(chart);
} catch (err) {
res.status(500).json({ error: String(err) });
}
});
app.listen(3000, () => console.log('http://localhost:3000'));

起動:

Terminal window
ASTROWAY_API_KEY=aw_test_... npx tsx src/server.ts

curl で確認:

Terminal window
curl -X POST http://localhost:3000/api/chart \
-H "Content-Type: application/json" \
-d '{"date":"1990-07-14","time":"14:30:00","timezoneOffset":3,"latitude":50.4501,"longitude":30.5234}'

完全な JSON のネイタルチャートが返ってくる。

どんなフレームワークでも OK。例として、シンプルなフォームを持つ React を使う:

import { useState } from 'react';
type Chart = {
planets: { name: string; longitude: number; isRetrograde: boolean }[];
aspects: { planet1: string; planet2: string; type: { name: string }; orb: number }[];
};
// Planets come with ecliptic longitude, not a sign: the sign is that longitude
// divided by 30. Work it out on your side, it is cheaper than another call.
const SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'];
const signOf = (longitude: number) => SIGNS[Math.floor(longitude / 30) % 12];
export function NatalForm() {
const [chart, setChart] = useState<Chart | null>(null);
async function handleSubmit(formData: FormData) {
const body = Object.fromEntries(formData.entries());
const res = await fetch('/api/chart', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
date: body.date,
time: body.time + ':00',
timezoneOffset: Number(body.tz),
latitude: Number(body.lat),
longitude: Number(body.lng),
}),
});
setChart(await res.json());
}
return (
<div>
<form action={handleSubmit}>
<input name="date" type="date" required />
<input name="time" type="time" required />
<input name="tz" type="number" placeholder="Timezone offset (e.g. 3)" required />
<input name="lat" type="number" step="0.0001" placeholder="Latitude" required />
<input name="lng" type="number" step="0.0001" placeholder="Longitude" required />
<button>Build chart</button>
</form>
{chart && (
<div>
<h2>Planets</h2>
<ul>
{chart.planets.map(p => (
<li key={p.name}>{p.name}: {signOf(p.longitude)} {(p.longitude % 30).toFixed(2)}°{p.isRetrograde ? ' R' : ''}</li>
))}
</ul>
<h2>Aspects</h2>
<ul>
{chart.aspects.map((a, i) => (
<li key={i}>{a.planet1} {a.type.name} {a.planet2} (orb {a.orb.toFixed(2)}°)</li>
))}
</ul>
</div>
)}
</div>
);
}

相性診断は次に obvious な機能だ。src/server.ts に追加:

const SynastryInput = z.object({
chart1: BirthInput,
chart2: BirthInput,
});
app.post('/api/synastry', async (req, res) => {
const parsed = SynastryInput.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: parsed.error });
const result = await client.synastry.compute(parsed.data);
res.json({
score: result.compatibility.score,
label: result.compatibility.label,
aspects: result.crossAspects,
});
});

これだけ。1 つのエンドポイント、1 回の呼び出しで 50 クレジット、0〜100 のスコアと cross-aspects を返す。

ステップ 4:デイリーホロスコープ

Section titled “ステップ 4:デイリーホロスコープ”

コンテンツによるエンゲージメントには、デイリーホロスコープが must have だ。AstroWay は実際のトランジットデータから生成する:

// The daily horoscope is keyed by sign and date, not by the birth chart.
const DailyInput = z.object({
sign: z.enum(['aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo',
'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces']),
date: z.string(),
});
app.post('/api/horoscope/daily', async (req, res) => {
const parsed = DailyInput.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: parsed.error });
const horoscope = await client.horoscope.daily({ ...parsed.data, language: 'uk' });
res.json({
text: horoscope.horoscope,
disclaimer: horoscope.disclaimer, // must be shown in the UI!
});
});

重要:AI 生成コンテンツには disclaimer が含まれており、(Terms of Service に基づき)ユーザーへの表示が義務付けられている。削除しないこと。

デイリーホロスコープは、同じ誕生日で似たネイタルチャートを持つユーザー全員で同一だ。積極的にキャッシュしよう:

import { LRUCache } from 'lru-cache';
const cache = new LRUCache<string, any>({ max: 1000, ttl: 86400 * 1000 });
app.post('/api/horoscope/daily', async (req, res) => {
const key = JSON.stringify(req.body);
const cached = cache.get(key);
if (cached) return res.json(cached);
const horoscope = await client.horoscope.daily(req.body);
cache.set(key, horoscope);
res.json(horoscope);
});

AstroWay にも組み込みの 5 分間サーバーキャッシュ(X-Cache: HIT)があるが、長寿命コンテンツには独自のキャッシュが必要だ。

どのプラットフォームでも OK。クイックスタートなら Vercel か Railway:

Terminal window
# vercel.json
{ "functions": { "src/server.ts": { "runtime": "@vercel/node" } } }

プラットフォームのダッシュボードで env ASTROWAY_API_KEY を設定しよう。

ここまで来たら、ローンチ前に月間のクレジット消費量を見積もろう:

credits/month = DAU × (avg_charts_per_user × 20
+ avg_synastries × 50
+ avg_horoscopes × 20) × 30

100 DAU で、1 ユーザーあたり 1 日にチャート 1 回 + ホロスコープ 1 回 + シナストリー 0.3 回の場合:

100 × (20 + 20 + 0.3 × 50) × 30 = 165,000 credits/month

これは Starter プラン の $19/月(200K クレジット)で収まる。

成長に合わせて追加したい高度な機能:

  • トランジットのオーバーレイ:/v1/transits で現在の惑星位置をネイタルチャートに重ねて表示
  • プログレッション:/v1/progressions で心理的成長の弧を描く
  • Solar return:/v1/solar-return で「誕生日からの 1 年の予測」
  • Human Design:Human Design API でまったく新しい機能領域が開ける
  • アストロカートグラフィー:/v1/acg でユーザーが住むのに適した場所をマッピング

どの機能もゼロから実装するのは大変だが、AstroWay なら API 1 コールで済む。

MakSeong · AstroWay

AstroWay API を作っている: Swiss Ephemeris を純粋な REST にラップし、実際に重要な退屈な詳細を書いています。

// この上に構築

Solar Fireと同じSwiss Ephemeris - 4行のコードで。

無料のキー(カード不要)。最初の支払いまでに月5,000回の呼び出し。

より多くのブログ →

Ephemeris 2026-07-19

精度をどう管理しているか: swetest と NASA に対する CI

占星術 API の精度は、天体暦まわりのリファクタリング 1 回で簡単に劣化します。その防御策を解説します: アプリと API で共有する単一の Swiss Ephemeris コア、基準チャートに対する数百の固定スナップショット、そして PR ごとの swetest CGI、Kerykeion、Prokerala、NASA 日食カタログとの三角測量。

Engineering 2026-07-15

3つの公式SDK: TypeScript、Python、PHP - 生のcurlの代わりに

生のHTTPは動作しますが、型付けされたクライアントは時間を節約します: パスの自動補完、リクエストとレスポンスの型、408/409/429/5xx用の組み込みリトライ、Stainlessスタイルのエラーヒエラルキー。3つの公式SDK - @astroway/sdk (npm)、astroway (PyPI)、astroway/sdk (Packagist) - と、それらが1つのOpenAPI契約から生成される方法を解説します。

Industry 2026-06-05

Free Astrology API: Which One Has the Best Free Tier in 2026?

A side-by-side of free tiers across the major astrology APIs - credits, request caps, card requirements - and how much you can actually build for free.