API documentation · v1
Lyric Scoring API
Submit any song lyric, get back a 12-metric score from the same rubric that grades every forged song on SongForgeAI. Useful for labeling generated output, A/B-testing your own prompts, or gating publishing workflows on a minimum composite score.
Early access. v1 ships with a single endpoint + sync scoring. Batch scoring + webhooks land in v1.1. Authentication is API-key based (
sfai_live_…).Endpoint
POST /api/v1/scoreAuthentication
Every request must include a Bearer token in the Authorization header. Keys start with sfai_live_.
Authorization: Bearer sfai_live_a1b2c3d4e5f6…Alternate header X-SFAI-Key is also accepted. Keys are issued per-user on the Professional tier; contact support to provision.
Request body
JSON with a required lyrics field (50–50,000 chars) and optional genre + title.
curl -X POST https://songforgeai.com/api/v1/score \
-H "Authorization: Bearer sfai_live_<YOUR_KEY>" \
-H "Content-Type: application/json" \
-d '{
"lyrics": "I walked the long road home tonight\nThe porch light burned the dark in two\n...",
"genre": "country",
"title": "Porch Light"
}'Response shape
Successful responses return 200 OK with:
{
"compositeScore": 78.4,
"grade": "B+",
"percentile": "Top 18%",
"metrics": [
{ "shortName": "Specificity", "score": 82 },
{ "shortName": "Emotional Truth", "score": 75 },
{ "shortName": "Craft", "score": 80 },
...
],
"wounds": [
"Bridge lacks perspective shift — feels like a recycled verse.",
"Final chorus doesn't earn the 'home' landing."
],
"transcendentLines": [
"The porch light burned the dark in two"
],
"version": "v1",
"generatedAt": "2026-04-21T12:34:56Z"
}compositeScore— 0-100 weighted sum of the 12 metrics.grade— S+ / S / A+ / … / F per the public grading scale.percentile— "Top N%" label (null for scores below 56).metrics[]— per-metric scores (Specificity, Emotional Truth, Imagery, etc).
Node.js example
const res = await fetch('https://songforgeai.com/api/v1/score', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SFAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
lyrics: draftLyrics,
genre: 'indie folk',
}),
});
const data = await res.json();
console.log(`Composite ${data.compositeScore} (${data.grade})`);Async mode (for batch pipelines)
Scoring a lyric takes 30–60s. For batch workloads, submit async jobs and either poll or receive a webhook when each completes.
// 1. Enqueue
POST /api/v1/score/async
{
"lyrics": "...",
"genre": "country",
"webhookUrl": "https://your-app.com/sfai-webhook"
}
// → 202 { "jobId": "abc...", "status": "queued", "pollUrl": "..." }
// 2a. Poll (auth with the same bearer key)
GET /api/v1/score/jobs/abc...
// → 200 { "status": "succeeded", "result": { ... } }
// 2b. Or receive a webhook (POST to webhookUrl):
// Header: X-SFAI-Event: score.completed
// Body: { "jobId": "abc...", "status": "succeeded", "result": { ... } }- Jobs are tied to the submitting key; only that key can poll.
- Webhook URLs must be
https://. One delivery attempt per terminal state; the poll endpoint is the retry path. - Lyric text is echoed back to Supabase for job correlation; excluded from poll responses.
Error codes
| Status | error | Meaning |
|---|---|---|
| 401 | missing_api_key | No token provided. |
| 401 | invalid_api_key_format | Token malformed. |
| 401 | unknown_api_key | Token not found in our DB. |
| 401 | revoked_api_key | Token was revoked. |
| 400 | invalid_lyrics | Lyrics missing or too short. |
| 400 | lyrics_too_long | Max 50,000 chars. |
| 429 | rate_limit_exceeded | 60 req/hour/key floor. |
| 502 | eval_output_unparseable | Upstream model error. Retry. |
| 503 | service_not_configured | Maintenance mode. |
Rate limits
- 60 requests per rolling hour per key on the Professional tier.
- Higher limits available on annual enterprise agreements.
- Every response includes an
X-SFAI-Versionheader for client-side version pinning.