Partner Connect lets the customers of a distributor or label platform ("partner") use ARCHITECT music in one click, and lets the partner show ARCHITECT insights in its own dashboard. This document is the contract between ARCHITECT and a partner. It is published at https://architectmusic.ai/partners/developers.
- Customer β ARCHITECT: the customer's catalog, streaming figures and earnings come from the partner (read-only, with the customer's consent).
- ARCHITECT β partner: trend scores, spike alerts and campaign status of customers who agreed to share them, via the Partner API, webhooks and an embeddable widget.
Contact: [email protected]
1. Integration options
| Option | Partner builds | Customer experience |
|---|---|---|
| A. OAuth (recommended) | OAuth 2.1 authorization server + the read endpoints in Β§2 | "Continue with {partner}" β consent β done |
| B. Signed link + push | Sign a JWT (Β§3), push catalog JSON (Β§4.5) | Click a link in the partner dashboard β done |
| C. File import | Nothing | Customer uploads DDEX ERN or CSV in ARCHITECT |
All options share the customer terms (trial, discount) and the partner landing page https://architectmusic.ai/partners/{slug}. Entry link for customers: https://architectmusic.ai/connect/{slug}.
2. Option A β OAuth
ARCHITECT is a public OAuth client (no secret) using the authorization code flow with PKCE S256.
| Item | Value |
|---|---|
| Redirect URI | https://architectmusic.ai/api/partners/{slug}/callback (+ http://localhost:3000/api/partners/{slug}/callback for development) |
| Grant types | authorization_code, refresh_token (rotation supported: ARCHITECT always stores the newest refresh token and serialises refreshes) |
| Token request | application/x-www-form-urlencoded: grant_type, client_id, code, code_verifier, redirect_uri, optional resource (RFC 8707) |
| Token response | { access_token, refresh_token, expires_in, scope } |
| Revocation | RFC 7009 endpoint (optional); ARCHITECT calls it when the customer disconnects |
| Errors | invalid_grant on a dead grant β ARCHITECT then marks the connection revoked and tells the customer |
Scopes
| Scope | Needed for |
|---|---|
read:profile | Required. Β§2.1 β stable account id + verified e-mail (sign-up / sign-in) |
read:releases | Releases + tracks (Β§2.2, Β§2.3) |
read:artists | Artists (Β§2.4) |
read:stats | Release statistics (Β§2.5) |
read:earnings | Earnings balance (Β§2.6) |
Only read scopes. ARCHITECT never writes to a partner platform.
Endpoints (relative to the partner's API base, Bearer access token)
All responses are JSON. List endpoints may answer either an array or { "data": [...] }. Unknown fields are ignored; missing optional fields are fine.
2.1 GET /me β scope read:profile
{ "account_id": "c0a8β¦", "email": "[email protected]", "email_verified": true,
"display_name": "Ana Bell", "org_name": "Bell Records", "locale": "de" }
account_id must be stable for the lifetime of the account. ARCHITECT creates an account only for email_verified: true, and never links to an existing ARCHITECT account by e-mail alone.
2.2 GET /releases?limit=100&offset=0 β read:releases
{ "data": [ { "id": "r1", "title": "Night Drive", "upc": "4061707000000", "release_date": "2026-05-02",
"cover_url": "https://β¦", "artists": ["Ana Bell"], "primary_genre": "Pop", "status": "live" } ],
"pagination": { "limit": 100, "offset": 0, "count": 1 } }
ARCHITECT pages until a page is shorter than limit. Imported statuses: live, processing, scheduled, delivered, approved (drafts and takedowns are skipped).
2.3 GET /tracks?release_id={id} β read:releases
[ { "id": "t1", "release_id": "r1", "title": "Night Drive", "version": "Radio Edit",
"isrc": "DEA1B2600001", "duration_ms": 201000, "is_explicit": false } ]
The ISRC is the join key: ARCHITECT matches it on Spotify and de-duplicates the customer's catalog by it.
2.4 GET /artists?limit=100 β read:artists
[ { "id": "a1", "name": "Ana Bell", "spotify_artist_id": "1AbCβ¦" } ]
2.5 GET /stats/release/{id}?range=30d β read:stats
{ "range": { "from": "2026-10-01", "to": "2026-10-30" },
"totals": { "total_streams": 125000, "revenue_eur": 312.4 },
"by_dsp": [ { "dsp": "spotify", "streams": 98000 } ] }
ARCHITECT stores these figures as first-party data in separate fields (partnerStreams30d, partnerRevenueEur30d) and distributes a release's totals across its tracks by their stream share. They never overwrite ARCHITECT's own platform metrics.
2.6 GET /earnings/balance β read:earnings
{ "currency": "USD", "lifetime_earned": 1840.2, "paid_out": 1200, "available": 640.2 }
ARCHITECT retries 429 (honouring Retry-After), refreshes once on 401, and treats 404/403 on optional reads as "no data".
3. Option B β signed link (handoff JWT)
The partner sends the customer to:
https://architectmusic.ai/connect/{slug}?h={JWT}
The JWT is HS256, signed with the handoff secret ARCHITECT issues to the partner (partner portal β Integration).
| Claim | Required | Meaning |
|---|---|---|
sub | yes | Stable account id at the partner (β€ 200 chars) |
email | yes | The customer's e-mail |
email_verified | yes, must be true | |
name | no | Display name |
locale | no | e.g. de |
jti | yes | Unique id, 8β200 chars; single use |
exp | yes | β€ 10 minutes in the future |
Example (Node.js):
import { createHmac, randomUUID } from 'node:crypto';
const b64 = (o) => Buffer.from(JSON.stringify(o)).toString('base64url');
const head = b64({ alg: 'HS256', typ: 'JWT' });
const body = b64({ sub: account.id, email: account.email, email_verified: true, name: account.name,
jti: randomUUID(), exp: Math.floor(Date.now() / 1000) + 300 });
const sig = createHmac('sha256', process.env.ARCHITECT_HANDOFF_SECRET).update(`${head}.${body}`).digest('base64url');
const url = `https://architectmusic.ai/connect/${SLUG}?h=${head}.${body}.${sig}`;
After the customer arrives, the partner pushes the catalog with Β§4.5.
4. Partner API (ARCHITECT β partner)
Base URL https://architectmusic.ai/api/partner/v1. Authentication: Authorization: Bearer {partner key} (partner portal β Integration; shown once, stored as a hash). Rate limit: 120 requests/minute per key (429 + Retry-After).
{accountId} is the customer's account id at the partner (account_id / sub). An account that is not connected, or whose owner switched sharing off, answers 404 β the API never reveals whether a customer exists.
Every read endpoint covers only the customer's tracks that were imported from your platform (and the spikes and campaigns of those tracks) β never the rest of their ARCHITECT catalog.
4.1 GET /accounts/{accountId}/tracks
{ "data": [ { "isrc": "DEA1B2600001", "title": "Night Drive", "artist": "Ana Bell", "partnerTrackId": "t1",
"trendScore": 74, "growthRate": 0.18, "spotifyStreams": 473964,
"platforms": { "spotify": { "currentStreams": 2210000, "popularity": 61 },
"tiktok": { "videoCount": 1200, "totalViews": 5400000 },
"youtube": { "videoCount": 85, "totalViews": 830000 } },
"partner": { "streams30d": 62500, "revenueEur30d": 156.2 },
"updatedAt": "2026-10-30T06:10:00Z" } ] }
Every field may be null (or a platform may be missing) when ARCHITECT has no data for it yet. partner echoes the figures your own platform delivered.
4.2 GET /accounts/{accountId}/spikes
Active spike alerts (only alerts that passed ARCHITECT's data-quality guards).
{ "data": [ { "id": "sp_β¦", "isrc": "DEA1B2600001", "title": "Night Drive", "artist": "Ana Bell",
"platform": "tiktok", "severity": "high", "reason": "TikTok videos 3.1Γ the 7-day baseline",
"detectedAt": "2026-10-29T18:00:00Z" } ] }
4.3 GET /accounts/{accountId}/campaigns
{ "data": [ { "id": "c1", "name": "Night Drive β launch", "status": "active", "platforms": ["meta", "spotify"],
"startDate": "2026-10-20", "endDate": "2026-11-20",
"stats": { "spend": 412.5, "impressions": 180000, "clicks": 3100 } } ] }
4.4 POST /accounts/{accountId}/embed-token
{ "token": "β¦", "url": "https://architectmusic.ai/embed/partner/β¦", "expiresAt": "2026-10-30T10:15:00Z" }
Mint it server-side for each page view; the token lives 15 minutes.
4.5 PUT /accounts/{accountId}/catalog (option B partners)
Replaces nothing β it upserts. Tracks are matched by your track id, then ISRC.
{ "releases": [ { "id": "r1", "title": "Night Drive", "upc": "4061707000000", "release_date": "2026-05-02",
"cover_url": "https://β¦", "artists": ["Ana Bell"], "genre": "Pop",
"tracks": [ { "id": "t1", "title": "Night Drive", "version": null, "isrc": "DEA1B2600001",
"duration_ms": 201000, "explicit": false } ] } ] }
Answer: { "summary": { "releases": 1, "tracksSeen": 1, "tracksCreated": 1, "tracksUpdated": 0, "tracksPendingSpotify": 0 } }. Max 500 tracks per request.
4.6 POST /accounts/{accountId}/stats (option B partners)
{ "releases": [ { "id": "r1", "from": "2026-10-01", "to": "2026-10-30", "total_streams": 125000, "revenue_eur": 312.4 } ] }
OpenAPI: GET /api/partner/v1/openapi.json.
5. Webhooks (optional)
ARCHITECT POSTs events to the partner's webhook URL:
POST {webhookUrl}
Content-Type: application/json
X-Architect-Event: spike.detected
X-Architect-Signature: t=1761818400,v1=5f2cβ¦
v1 = hex HMAC-SHA256 of "{t}.{raw body}" with the partner's webhook secret. Reject a timestamp older than 5 minutes. ARCHITECT retries a non-2xx answer up to three times over the following hours (with growing delays); answer 2xx fast and process asynchronously. Deliveries can repeat β de-duplicate by data.id.
{ "event": "spike.detected", "accountId": "c0a8β¦",
"data": { "id": "sp_β¦", "isrc": "DEA1B2600001", "title": "Night Drive", "artist": "Ana Bell",
"platform": "tiktok", "severity": "high", "reason": "TikTok videos 3.1Γ the 7-day baseline",
"detectedAt": "2026-10-29T18:00:00Z" } }
6. Widget
<iframe src="{url from Β§4.4}" style="width:100%;height:420px;border:0" title="ARCHITECT insights" loading="lazy"></iframe>
The page may only be framed by the partner's registered origins (CSP frame-ancestors). Add ?theme=dark for dark dashboards.
7. Checklist for a new partner
- Tell us your slug, name, logo (SVG/PNG), brand colour, website and the customer terms you want.
- Option A: send your authorize, token, revoke URLs and API base; we register as a client. Option B: we issue the handoff secret.
- Register the origins that may embed the widget and (optional) your webhook URL.
- Collect the partner key in the partner portal (
https://architectmusic.ai/partner). - Test with a sandbox account: connect β catalog appears β Partner API answers β widget renders.
- Go live: we switch the partner to active; your landing page goes live at
/partners/{slug}.
Questions or a sandbox? [email protected]