Skip to content
Partner Connect

Developer documentation

Everything a partner needs to connect: the Partner Connect Spec, the signed handoff link, catalog push, the Partner API, webhooks and the widget.

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

OptionPartner buildsCustomer experience
A. OAuth (recommended)OAuth 2.1 authorization server + the read endpoints in Β§2"Continue with {partner}" β†’ consent β†’ done
B. Signed link + pushSign a JWT (Β§3), push catalog JSON (Β§4.5)Click a link in the partner dashboard β†’ done
C. File importNothingCustomer 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.

ItemValue
Redirect URIhttps://architectmusic.ai/api/partners/{slug}/callback (+ http://localhost:3000/api/partners/{slug}/callback for development)
Grant typesauthorization_code, refresh_token (rotation supported: ARCHITECT always stores the newest refresh token and serialises refreshes)
Token requestapplication/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 }
RevocationRFC 7009 endpoint (optional); ARCHITECT calls it when the customer disconnects
Errorsinvalid_grant on a dead grant β€” ARCHITECT then marks the connection revoked and tells the customer

Scopes

ScopeNeeded for
read:profileRequired. Β§2.1 β€” stable account id + verified e-mail (sign-up / sign-in)
read:releasesReleases + tracks (Β§2.2, Β§2.3)
read:artistsArtists (Β§2.4)
read:statsRelease statistics (Β§2.5)
read:earningsEarnings 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).

ClaimRequiredMeaning
subyesStable account id at the partner (≀ 200 chars)
emailyesThe customer's e-mail
email_verifiedyes, must be true
namenoDisplay name
localenoe.g. de
jtiyesUnique id, 8–200 chars; single use
expyes≀ 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

  1. Tell us your slug, name, logo (SVG/PNG), brand colour, website and the customer terms you want.
  2. Option A: send your authorize, token, revoke URLs and API base; we register as a client. Option B: we issue the handoff secret.
  3. Register the origins that may embed the widget and (optional) your webhook URL.
  4. Collect the partner key in the partner portal (https://architectmusic.ai/partner).
  5. Test with a sandbox account: connect β†’ catalog appears β†’ Partner API answers β†’ widget renders.
  6. Go live: we switch the partner to active; your landing page goes live at /partners/{slug}.

Questions or a sandbox? [email protected]