""" The read surface of the Audio Audit API, composed from the accounts, audio, billing, blog and metrics schemas. Much of it answers without credentials — pricing, the check, artifact and enhancement catalogues, report templates, blog posts, feature flags, credit estimates, podcast search, and a report or series fetched by id — while the fields that list or reach a workspace's own data need a session cookie, an API key sent as `Authorization: Bearer `, or a JWT sent as `Authorization: JWT `. Those fields answer for the caller's personal workspace unless an `organisationId` argument names one of their organisations. """ type Query { """Staff only. The latest nightly snapshot.""" founderDashboard: FounderDashboardType """ Staff only. JSON: queue depth, worker keepalive age and today's counts, computed now. """ founderLive: String post(id: UUID, slug: String): PostType posts(category: String, ordering: String): [PostType] plans: [PlanType] plan(slug: String): PlanType creditPacks: [CreditPackType] checkTypes: [CheckTypeType] artifactTypes(userSelectable: Boolean): [ArtifactTypeType] enhancementTypes: [EnhancementTypeType] reportTemplates(organisationId: UUID): [ReportTemplateType] creditBalance(organisationId: UUID): String creditHistory(limit: Int, organisationId: UUID): [CreditTransactionType] estimateReportCredits(durationMs: Float, checkIds: [UUID], artifactIds: [UUID], enhancementIds: [UUID], reportTemplateId: UUID): String report(id: UUID): ReportType reports( limit: Int offset: Int organisationId: UUID """ Legacy filter by show, outside the workspace branches: every report on this series, or nothing at all if you cannot see the series. Wins over `forSeriesId` when both are given. Unsupported for integrations — use `forSeriesId` with `organisationId`. """ seriesId: UUID podcastId: UUID @deprecated(reason: "Use `seriesId`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ Narrow the listing to one show within the workspace the query is scoped to (`organisationId`, or your personal workspace). Ignored when `seriesId` is given. """ forSeriesId: UUID forPodcastId: UUID @deprecated(reason: "Use `forSeriesId`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") status: String createdSince: DateTime updatedSince: DateTime ): [ReportType] """ One series by id — a show being audited: a podcast today; YouTube channels and audiobooks are planned kinds. Answers without credentials to whoever holds the id, like a report; through `organisationId` the workspace must hold the show and the calling member's grants decide. """ series(id: UUID, organisationId: UUID): SeriesType """ The series a workspace tracks — each a show being audited: a podcast today; YouTube channels and audiobooks are planned kinds. The calling user's personal subscriptions, or an organisation's shows when `organisationId` names one of theirs (a member sees the shows they hold a grant on); `all: true` spans every workspace they belong to. `limit` defaults to 20; pass `offset` to page. """ seriesList(limit: Int, organisationId: UUID, all: Boolean, offset: Int): [SeriesType] podcast(id: UUID, organisationId: UUID): SeriesType @deprecated(reason: "Use `series`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") podcasts(limit: Int, organisationId: UUID, all: Boolean, offset: Int): [SeriesType] @deprecated(reason: "Use `seriesList`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") podcastSearch(text: String): String feedCandidate(url: String!): String enhancement(id: UUID): EnhancementJobType enhancements(limit: Int, organisationId: UUID): [EnhancementJobType] """ Chapters generated recently in this workspace for audio of the same length, offered for reuse on a new upload. Newest first. """ chapterGenerationCandidates(durationMs: Int!, organisationId: UUID): [ChapterGenerationType] user(id: UUID): UserType featureFlags: [FeatureFlagType] prices: String pricesPreview(productIds: [String]!, currency: String!): String organisations: [OrganisationType] organisation(id: UUID): OrganisationType teams(organisationId: UUID): [UserType] retrieveInvite(email: String, inviteToken: String): UserType invoice(transactionId: String!): String apiKeys: [ApiKeyType] } """ The latest nightly founder-dashboard snapshot. Staff only; every field is null until the first snapshot exists. """ type FounderDashboardType { """Day the snapshot covers. Null until the first snapshot exists.""" snapshotDate: Date """ When the snapshot was last written. Null until the first snapshot exists. """ generatedAt: DateTime """JSON: the snapshot document. Null until the first snapshot exists.""" payload: String """ JSON: [{section, error}] for sections that failed on the last run. Null until the first snapshot exists. """ sectionErrors: String } """ The `Date` scalar type represents a Date value as specified by [iso8601](https://en.wikipedia.org/wiki/ISO_8601). """ scalar Date """ The `DateTime` scalar type represents a DateTime value as specified by [iso8601](https://en.wikipedia.org/wiki/ISO_8601). """ scalar DateTime """ An article on the public Audio Audit blog. Needs no authentication: `posts` returns published articles whose `created_at` has already arrived, but `post` will return a single unpublished draft, or a published post scheduled in the future, to anyone who knows its id or slug. """ type PostType { id: UUID! createdAt: DateTime updatedAt: DateTime createdBy: UserType updatedBy: UserType title: String! slug: String! status: BlogPostStatusChoices! metaDescription: String content: String! """ For including JS and similar at bottom of the page. Will not be run through Markdown processor. """ nonVisibleContent: String! intro: String updatedAtCombined: String absoluteUrl: String contentFormatted: String headerImage: String category: String } """ Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects in fields, resolvers and input. """ scalar UUID """ A person's account, and a billable entity in its own right: every user has a personal workspace with its own plan, credit balance and podcasts, alongside any organisation they belong to. """ type UserType { firstName: String! lastName: String! """ Whether this account is staff. Visible only to the account holder (or staff). """ isStaff: Boolean dateJoined: DateTime! id: UUID! preferredCurrency: String email: String! displayEmail: String! passwordSet: Boolean! emailNotificationBilling: Boolean! emailNotificationReportCreationMe: Boolean! emailNotificationReportCreationOthers: Boolean! emailNotificationReportCreationFeed: Boolean! emailNotificationProductAnnouncements: Boolean! emailNotificationPromotions: Boolean! """ JSON: the older audio-duration quota for this user's personal workspace (allowance, used and remaining milliseconds, and when the period ends). Superseded by `credits`. Visible only to the account holder (or staff). """ quota: String """ JSON: the personal workspace credit position — current balance, the monthly plan allowance and how much of it is used, whether that allowance is the free tier's, the separately-expiring top-up balance with each top-up's remaining credits and expiry date, and when the allowance next resets. Visible only to the account holder (or staff). """ credits: String """ JSON: the personal workspace's live Paddle subscription, or null if there is none. Read from Paddle when requested, not from our database. Visible only to the account holder (or staff). """ subscription: String """ JSON: the plan behind the live Paddle subscription, or null on the free tier. Read from Paddle when requested. Visible only to the account holder (or staff). """ plan: String """ JSON: payment history for the live Paddle subscription, or null if there is none. Read from Paddle when requested. Visible only to the account holder (or staff). """ payments: String """ JSON: the podcast cap and current usage for the personal workspace. A personal workspace has no seats, so the seat keys are null. Triggers a live Paddle plan lookup, so avoid requesting it from a query that runs on every page. Visible only to the account holder (or staff). """ limits: String """ Gravatar URL derived from the account email, falling back to a generated identicon. Always returns a URL, whether or not the person has a Gravatar. """ avatarUrl: String """ The organisations this user is a member of. The account holder (and staff) see them all; anyone else sees only the ones they share with this user, and an anonymous caller sees none. """ organisations: [OrganisationType] """ This user's role in the named organisation — `owner`, `admin` or `member`. Null unless the caller is this user or a fellow member. Defaults to the organisation the user was reached through, so it can be omitted inside a `teams(organisationId:)` query. """ organisationRole(organisationId: UUID): String """ Workspace-scoped capabilities granted to this user on top of their role, e.g. `org.report`. Null unless the caller is this user or an admin of the organisation — plain members see roles, not spend rights. """ organisationExtraPermissions(organisationId: UUID): [String] """ True while a staff member is viewing the site as another account. A property of the calling session rather than of this user, so it reads the same on every `UserType` in a response. """ isFaked: Boolean } """ A shared workspace — a production company, studio or team — with its own members, podcasts, plan and credit balance, kept separate from any member's personal workspace. Several fields answer from the calling user's point of view rather than describing the organisation in the abstract: `role`, `isOwner` and `extraPermissions` all report on the caller's own membership. """ type OrganisationType { id: UUID! preferredCurrency: String name: String! """Whether the calling user owns this organisation.""" isOwner: Boolean """ The calling user's role here — `owner`, `admin` or `member`. Null if they are not a member. """ role: String """ Workspace-scoped capabilities granted to the calling user on top of their role, e.g. `org.report`. The caller's own membership only — never another member's. """ extraPermissions: [String] """ JSON: the older audio-duration quota for this workspace (allowance, used and remaining milliseconds, and when the period ends). Superseded by `credits`. Visible only to a member of this workspace (or staff). """ quota: String """ JSON: this workspace's credit position — current balance, the monthly plan allowance and how much of it is used, whether that allowance is the free tier's, the separately-expiring top-up balance with each top-up's remaining credits and expiry date, and when the allowance next resets. Visible only to a member of this workspace (or staff). """ credits: String """ JSON: the workspace's live Paddle subscription, or null if there is none. Read from Paddle when requested, not from our database. The owner sees the whole object; any other member, admins included, sees only its `status` and `next_billed_at`, and anyone else sees null. """ subscription: String """ JSON: the plan behind the live Paddle subscription, or null on the free tier. Read from Paddle when requested. Visible only to a member of this workspace (or staff). """ plan: String """ JSON: payment history for the live Paddle subscription, or null if there is none. Read from Paddle when requested. Visible only to the owner of this workspace. """ payments: String """ JSON: the plan seat and podcast caps with current usage, and whether they are being enforced for the calling user. Triggers a live Paddle plan lookup, so avoid requesting it from a query that runs on every page. Visible only to a member of this workspace (or staff). """ limits: String } """An enumeration.""" enum BlogPostStatusChoices { """Draft""" DRAFT """Published""" PUBLISHED } """ A subscription tier as sold on the pricing page: its monthly credit allowance and its seat and podcast caps. Public and unfiltered — `plans` needs no authentication and returns every plan row, including any not currently on sale. """ type PlanType { id: UUID! name: String! slug: String! description: String! ordering: Int! paddleSubscriptionId: Int! """ Paddle price or product id used to map an active subscription to this plan. """ paddlePriceId: String! """ Credits granted each monthly billing period. Resets monthly, no rollover. """ monthlyCredits: Int! """ Maximum members per organisation on this plan, including pending invites. Null = unlimited. Ignored for personal workspaces. """ maxUsers: Int """Maximum shows linked to a workspace on this plan. Null = unlimited.""" maxSeries: Int conversionHours: Float """Maximum podcasts linked to a workspace on this plan. Null = unlimited.""" maxPodcasts: Int @deprecated(reason: "Use `PlanType.maxSeries`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") } """ A one-off credit top-up bought outside a subscription, for work that overruns the monthly allowance. Credits granted this way expire twelve months after purchase rather than at the end of the billing period. Public; only packs currently on sale are listed. """ type CreditPackType { id: UUID! name: String! slug: String! description: String! """Credits granted when this pack is purchased.""" credits: Int! """Paddle one-time price id for this pack.""" paddlePriceId: String! """Human-readable price, e.g. "$29".""" priceDisplay: String! ordering: Int! isActive: Boolean! } """ One quality check the analysis can run against an episode — loudness, true peak, noise floor, leading and trailing silence and so on. `creditRate` is only the marginal per-audio-minute cost of adding this check; the artifacts it needs are priced separately on `ArtifactTypeType`, and `requiredArtifactIds` resolves those dependencies transitively so a caller can price a selection without parsing `dependencies`. """ type CheckTypeType { id: UUID! name: String! verboseName: String! ordering: Int! measurementUnit: String! """ Comma-separated list of artifacts or measurements that need to complete first, in the format 'artifact.transcription' """ dependencies: String! creditRate: Float requiredArtifactIds: [UUID] } """ A derived output the analysis pipeline can produce for a report — waveform, transcription, cover image, file info. Most exist only as check dependencies; only those flagged `isUserSelectable` can be asked for as a deliverable in their own right. `creditRate` is per audio-minute and is charged once per report however many checks depend on it. """ type ArtifactTypeType { id: UUID! name: String! verboseName: String! """ Whether customers can select this artifact directly as a report deliverable (e.g. a transcript), rather than it only being pulled in as a check dependency. """ isUserSelectable: Boolean! creditRate: Float } """ An audio enhancement offered as a billable job in its own right — loudness normalisation, noise reduction, compression, metadata. Listed only to callers for whom the `enhancements` feature flag is on, and an entry with `isAvailable: false` is advertised but cannot yet be run. """ type EnhancementTypeType { id: UUID! name: String! verboseName: String! description: String! ordering: Int! """ When False the enhancement is shown but cannot be selected/processed yet. """ isAvailable: Boolean! creditRate: Float suggestedByChecks: [String] } """ A named set of checks that decides what a report runs and therefore what it costs. The `reportTemplates` query lists the ownerless system templates for everyone, plus the caller's own and — given an `organisationId` they belong to — that workspace's; the `enhancements` list is a UI suggestion only, each one selected becoming a separate, separately-billed job rather than part of the report charge. """ type ReportTemplateType { id: UUID! name: String! slug: String! """False for system-provided templates, True for customer-created ones.""" isCustom: Boolean! """The system default template used when none is specified.""" isDefault: Boolean! checks: [CheckTypeType!]! """ Suggested enhancements shown in the UI. Not billed with the report — each spawns a separate Enhancement job. """ enhancements: [EnhancementTypeType!]! destinationName: String checkIds: [UUID] enhancementIds: [UUID] } """ One movement in a workspace's credit ledger, and an immutable record once written. `amount` is signed — negative for charges and expiries — and `balanceAfter` is the workspace's whole balance immediately after it. The category of movement is published as `kind`, not `type`. Readable only by the workspace whose ledger it belongs to. """ type CreditTransactionType { created: DateTime! id: UUID! description: String! kind: String amount: Float balanceAfter: Float reportId: UUID enhancementId: UUID """ When the top-up lot behind this grant expires. Non-null only on a `grant` row whose lot is a top-up (twelve months after purchase); null on plan/free/override grants and on every charge, refund or adjustment row, including a charge against a top-up lot. """ expiresAt: DateTime } """ One audio analysis: an uploaded file or a podcast episode, the checks that ran against it, and everything derived from it — measurements, waveform, transcript, artwork. `report(id:)` looks one up by UUID without authentication, by design, so that a finished report can be shared by link; `reports` lists only what the caller's own workspace owns. """ type ReportType { status: AudioReportStatusChoices! id: UUID! createdAt: DateTime updatedAt: DateTime createdBy: UserType updatedBy: UserType originalFilename: String! artifactsComplete: Boolean! measurementsComplete: Boolean! """ The feed episode this report analysed, when it came from a monitored feed. Null for a direct upload. """ episode: EpisodeType user: UserType organisation: OrganisationType personalReport: Boolean! startedAt: DateTime """Analysis credits debited at completion. Null until charged.""" creditsCharged: Decimal """ Display name for the report: the title tag read from the audio, else the podcast episode title, else the uploaded filename. """ name: String """ JSON: the headline summary — how many checks passed and the overall percentage, plus an estimate of the milliseconds remaining while the report is still running. """ stats: String """URL of the analysed audio file.""" audioUrl: String """ JSON: the artwork embedded in the audio — `url`, `width`, `height`. An empty object if the file carried none. """ coverImage: String """ URL of the rendered waveform image, or null until that artifact has completed. """ waveform: String """ JSON: the tags read from the audio file (title, album, artist and so on). An empty object if the file carried none. """ metadata: String """ JSON: what ffprobe found — duration in milliseconds, codec, channels, sample rate. An empty object until that artifact has completed. """ fileInfo: String """ JSON: one entry per check that ran, with its measured value, whether it passed, and the warning text shown when it did not. """ results: String """ JSON: the speech-to-text transcript as a list of `start`/`end`/`text`/`speaker` phrases, with times in milliseconds. `speaker` is `"Speaker 1"`, `"Speaker 2"`, … in order of first appearance, or null when the report predates speaker detection or detection failed for it. The JSON literal `null` when the report has no completed transcription. """ transcript: String """ The show this report was generated for, when it came from a monitored feed. Null for a direct upload. """ series: SeriesType """ The show this report was generated for, when it came from a monitored feed. Null for a direct upload. """ podcast: SeriesType @deprecated(reason: "Use `ReportType.series`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") podcastEpisode: EpisodeType @deprecated(reason: "Use `ReportType.episode`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ JSON: the most recent completed chapter generation for this report — `chapters`, `model_used`, `created_at`, `credits_charged`. The JSON literal `null` when nothing has been generated, and for any caller outside the report's workspace. """ chapterGeneration: String """ Whether the calling user may start an enhancement from this report. False for anonymous callers and for anyone outside the report’s own workspace. Report UUIDs are public, so this gates the enhance panel on the report page. """ canEnhance: Boolean } """An enumeration.""" enum AudioReportStatusChoices { """pending""" PENDING """scheduled""" SCHEDULED """in_progress""" IN_PROGRESS """awaiting_review""" AWAITING_REVIEW """complete""" COMPLETE """failed""" FAILED """out_of_quota""" OUT_OF_QUOTA } """ An episode the feed checker has seen (REST API plan §3.3, §4.4). Rows exist only for the items a feed check has actually observed — recent episodes, not the show's full back catalogue — so a listing over this type is "what we have seen", not "what the feed contains". The field list is explicit rather than the whole model: `downloaded`, `download_priority`, `last_download_attempt`, `download_failures` and `download_abandoned_at` are our own scheduling bookkeeping, and the reverse relations to reports and feed checks belong to the resources that own them. `download_abandoned` below is the one derived exception — see its resolver. """ type EpisodeType { id: UUID! createdAt: DateTime audioUrl: String! guid: String pubDate: Date episodeTitle: String """ True once we have stopped retrying this episode’s audio download because the enclosure URL kept failing. The reason is deliberately not published — it can quote third-party content. """ downloadAbandoned: Boolean } """The `Decimal` scalar type represents a python Decimal.""" scalar Decimal """ A series is a show being audited — what the dashboard calls a podcast. A podcast is the only kind today; YouTube channels and audiobooks are planned. Owned by exactly one workspace — a `user` or an `organisation`, never both — so this is that workspace's own copy of the show, not a row shared with anyone else who happens to track the same feed (plan §2). How new episodes arrive lives on `sources`, which *is* shared between workspaces on the same feed; who else in the owning workspace is attached to the show lives on `members`. """ type SeriesType { id: UUID! createdAt: DateTime updatedAt: DateTime createdBy: UserType updatedBy: UserType """ What kind of show this series is: `PODCAST` today; YouTube channels and audiobooks are planned kinds. """ kind: SeriesKind! user: UserType organisation: OrganisationType """ The show's name. This workspace's own override if it set one, otherwise the channel title from the RSS feed, which the feed checker keeps fresh for every workspace on that feed. """ title: String """ Hex colour used for this show in charts, including the leading `#`. Resolves to this workspace's own override if it set one, then the feed's own colour, then one derived from the show's id — it is never null. """ color: String """ Channel artwork for this show. This workspace's own override if it set one, otherwise the artwork from the RSS feed, which the feed checker keeps fresh for every workspace on that feed — falling back to the cover image extracted from the newest report until the checker has stamped one. """ coverImageUrl: String """ Workspace members attached to this show, each with their per-show role and report-email preference. Empty for a personal show — it has no roster, its owner is the sole recipient — and for a caller who cannot see the show. """ members: [SeriesMemberType] """ How new content arrives for this show — its RSS feed today, carrying the feed URL and the feed checker's health counters. A series has at most one RSS source: always one when the show was added from a feed, and none on a show that has no feed. """ sources: [SourceType] """ JSON: of the five most recent episodes by publication date, those that have a report — each with its date, overall score and report id, oldest first. Episodes with no report are dropped rather than backfilled, so this can be empty while older reported episodes exist. `report_id` is null for a caller who cannot see this show; the dates and scores are the same for everyone. """ historicScores: String """ Whether this show's feed is polled and reports generated automatically. The switch belongs to the show, and so to the one workspace that owns it — no `organisationId` context is needed or read. """ automatedReporting: Boolean """ Episodes of this show, newest first. `limit` defaults to 20; pass `offset` to page through the rest. """ episodes(limit: Int, offset: Int): [EpisodeType] """ The RSS feed URL. Null when the show has no RSS source — a row staff created without one, or whose source was removed. """ feedUrl: String @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") lastCheck: DateTime @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """Last time the feed returned a parseable RSS document""" lastSuccessfulCheck: DateTime @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """Consecutive failed feed checks; reset to 0 on any success""" consecutiveFailures: Int! @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ Consecutive checks where the feed read fine but its newest episode could not be stored; reset to 0 on any successful ingest """ consecutiveIngestFailures: Int! @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """Last time the newest feed item was stored, or confirmed already stored""" lastSuccessfulIngest: DateTime @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ JSON: the last 24 hours of feed polls — timestamp, HTTP status, request duration in milliseconds, and the report each one produced for this show. An empty array unless this show has feed monitoring switched on. A poll's `report_id` is null when it found no new episode, when this show has no report on the episode it found, and for a caller who cannot see this show. """ historicFeedChecks: String @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ JSON: mean feed response time and uptime fraction over 24 hours, 7 days and 28 days, measured on the feed itself and so the same for every show on it. An empty array unless this show has feed monitoring switched on. """ feedCheckStats: String @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ True once the feed has failed to return a parseable RSS document on three consecutive polls. """ feedIsBroken: Boolean @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ True once three consecutive polls have read the feed successfully but failed to store its newest episode. The underlying error is deliberately not published — the fault is ours, not the feed owner's. """ ingestIsBroken: Boolean @deprecated(reason: "Use `SeriesType.sources`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") } """ What kind of show a series is. `PODCAST` today; YouTube channels and audiobooks are planned kinds. """ enum SeriesKind { PODCAST } """ One workspace member attached to one of that workspace's shows, carrying both their per-show access grant (`role`: `manager` or `viewer`) and their per-show report-email preference — the roster is the recipient list, so a show with nobody attached emails nobody. Names, roles and permissions are readable by anyone who can see the show; the email-status fields resolve to null for everyone but managers of the show and the member themselves. Replaces the three link-row types the pre-inversion schema published (plan §4.4): a show has one owner rather than one link row per customer, so there is one roster type rather than three — reached off `SeriesType.members` instead of off a per-tenant link row. """ type SeriesMemberType { id: UUID! createdAt: DateTime updatedAt: DateTime createdBy: UserType updatedBy: UserType user: UserType! emailReports: Boolean role: String feedEmailsPaused: Boolean emailVerified: Boolean extraPermissions: [String] } """ How new content arrives for a series — an RSS feed today (`kind: RSS`); Dropbox watch folders and other kinds are planned. Carries the feed URL and the feed checker's health counters, and is readable by whoever can read its series. """ type SourceType { id: UUID! createdAt: DateTime """ The RSS feed URL this source polls. Null for future kinds that have no feed. """ feedUrl: String """When the feed checker last polled this feed, successfully or not.""" lastCheck: DateTime """Last time the feed returned a parseable RSS document""" lastSuccessfulCheck: DateTime """Consecutive failed feed checks; reset to 0 on any success""" consecutiveFailures: Int! """ Consecutive checks where the feed read fine but its newest episode could not be stored; reset to 0 on any successful ingest """ consecutiveIngestFailures: Int! """Last time the newest feed item was stored, or confirmed already stored""" lastSuccessfulIngest: DateTime """ How this source delivers content: `RSS` today; further kinds are planned. """ kind: SourceKind! """ True once the feed has failed to return a parseable RSS document on three consecutive polls. """ feedIsBroken: Boolean """ True once three consecutive polls have read the feed successfully but failed to store its newest episode. The underlying error is deliberately not published — the fault is ours, not the feed owner's. """ ingestIsBroken: Boolean } """How a source delivers content. `RSS` today; further kinds are planned.""" enum SourceKind { RSS } """ A combined enhancement job (mirrors ReportType). ``types`` is exposed as plain name/id lists to avoid double-registering the EnhancementType model (the billing schema already owns EnhancementTypeType). """ type EnhancementJobType { status: AudioEnhancementStatusChoices! id: UUID! createdAt: DateTime originalFilename: String! personalReport: Boolean! startedAt: DateTime """ Credits debited at completion for this enhancement. Null until charged. """ creditsCharged: Decimal """ Names of the enhancement types run in this combined job, e.g. `noise_reduction`. """ typeNames: [String] """ Ids of the enhancement types run in this combined job, matching `enhancementTypes`. """ typeIds: [UUID] """ JSON: the options chosen for each selected enhancement — loudness preset, noise-reduction strength, compression amount, metadata tags and cover. An empty object if none were given. """ parameters: String """ JSON: the run record written by the processor — what it measured, what ran and why, and per-stage timings. An empty object until the job has produced one. """ results: String """ JSON: what ffprobe found in the input file — duration in milliseconds, codec, channels, sample rate. An empty object until the processor has read it. """ fileInfo: String """URL of the enhanced audio file. Null until the job reaches `complete`.""" outputUrl: String """ Tracked download link for the enhanced file, valid for 24 hours. Null until the job reaches `complete`. """ downloadUrl: String """ The report whose audio this job was started from, when it was created from an existing analysis rather than an upload. Null otherwise. """ sourceReportId: UUID """ JSON: the most recent completed chapter generation for this job — `chapters`, `model_used`, `credits_charged`. The JSON literal `null` when this job never generated any. """ chapterGeneration: String } """An enumeration.""" enum AudioEnhancementStatusChoices { """pending""" PENDING """scheduled""" SCHEDULED """in_progress""" IN_PROGRESS """awaiting_review""" AWAITING_REVIEW """complete""" COMPLETE """failed""" FAILED """out_of_quota""" OUT_OF_QUOTA } """ One completed chapter generation, offered back as a reuse candidate for a new upload of the same audio (plan §4.5). Deliberately narrow: what the editor needs to seed its rows and what the UI needs to label the option. The linked report is exposed as the ordinary ``ReportType`` so the label can read "Chapters from , "; it is null for a generation made on the dashboard path, where the label falls back to the date alone. """ type ChapterGenerationType { id: UUID createdAt: DateTime """ The report this generation was made from, for labelling the option. Null for a generation made on the dashboard path. """ report: ReportType """ JSON: the generated chapters as `title`/`start` objects, starts in milliseconds. """ chapters: String } """ The effective on/off state of one feature flag for whoever is asking, so a client can hide a feature that is switched off or still staff-only. Readable without authentication: anonymous callers get the full list of flags, with everything not generally available resolving to `false`. """ type FeatureFlagType { key: String enabled: Boolean } """ An API key granting programmatic access to the API on behalf of its owning workspace. The secret is not published on this type — only `maskedKey`, enough of the key to tell two apart and never enough to use one: a prefixed key shows `audioaudit_api_` plus its first eight body characters, a legacy unprefixed key its first eight characters — and the `apiKeys` query lists the caller's personal keys plus those of organisations they own. """ type ApiKeyType { id: UUID! createdAt: DateTime updatedAt: DateTime description: String! enabled: Boolean! lastUsedAt: DateTime maskedKey: String owner: OwnerUnion } """ Whichever workspace owns a resource: a user (their personal workspace) or an organisation (a shared one). Exactly one of the two, never both — ownership is what decides whose plan and credit balance a job is billed against. """ union OwnerUnion = UserType | OrganisationType """ The write surface of the Audio Audit API, composed from the accounts, audio, billing and metrics schemas. Almost every mutation reports failure in its payload — `ok: false` with a human-readable `error` — rather than raising, so a response without a top-level `errors` block does not on its own mean the write succeeded. """ type Mutation { """ The "How did you hear about us?" answer collected during onboarding. Lands on the row registration created, but get-or-creates so an account that predates attribution capture can still answer. """ attributionSelfReport(source: String!): AttributionSelfReport """ Returns the Paddle one-time checkout parameters for a credit pack. The actual credit grant happens later, from the verified transaction.completed webhook. """ purchaseCreditPack(organisationId: UUID, packId: UUID, slug: String): PurchaseCreditPack """Create or update a customer-owned custom report template.""" saveReportTemplate(checkIds: [UUID]!, enhancementIds: [UUID], id: UUID, name: String!, organisationId: UUID): SaveReportTemplate leaveFeedback(content: String, reportId: UUID!, starRating: Int): LeaveFeedback getUploadUrl(duration: Int!, fileName: String!, fileType: String!): GetUploadUrl startReport(durationMs: Float, organisationId: UUID, originalFilename: String, reportId: UUID!, reportTemplateId: UUID): StartReport reportFromFeed(itemNum: Int, organisationId: UUID, url: String!): ReportFromFeed retryReport(reportId: UUID!): RetryReport """ Mint an Enhancement id and a signed PUT URL for its input (and, when metadata+cover is coming, the cover source). Mirrors GetUploadUrl. """ getEnhancementUploadUrl(coverFileName: String, coverFileType: String, fileName: String!, fileType: String!): GetEnhancementUploadUrl """ Probe a file the browser has just uploaded, before any job is started: how long it is and what chapters it already carries (plan §4.5). A mutation rather than a query because it does real work on a named blob, and the dashboard calls it exactly once per upload. """ enhancementFileInfo(enhancementId: UUID!, fileName: String!): EnhancementFileInfo """ Create and queue a combined enhancement job from an uploaded file or a completed report's audio (server-side blob copy — no re-upload). """ startEnhancement(durationMs: Float, enhancementId: UUID, enhancementTypeIds: [UUID]!, organisationId: UUID, originalFilename: String, parameters: String, sourceReportId: UUID): StartEnhancement """ Propose chapters for a completed report from the transcript it already has. Synchronous by design (plan §2): the transcript exists, so the only wait is the model's, bounded by the read timeout in ``chapters.TIMEOUT``. Charged when — and only when — proposals land, so a failed run costs nothing and leaves no row. """ generateReportChapters(organisationId: ID, reportId: UUID!): GenerateReportChapters """ Finish the review step of an enhancement paused at ``awaiting_review`` (plan §4.5): write the chapters the user settled on into the job's parameters and let it run, or abandon the job. The generation has already been charged — it was billed the moment the proposals landed — so neither answer here costs anything on its own. Accepting lets the job reach ``complete``, which charges the file write; cancelling never does, because that charge only ever fires on the flip to ``complete``. """ acceptEnhancementChapters( """ Abandon the job instead of accepting chapters. The file is never written and the write is never charged. """ cancel: Boolean """ JSON: the chapters to write, as `title`/`start` objects with starts in milliseconds. Omit to accept the proposals unchanged; an empty list finishes the job without writing any chapters. """ chapters: String enhancementId: UUID! ): AcceptEnhancementChapters """ Add a show to a workspace, adopt one another customer already tracks, or edit its settings. `feedUrl` creates the series and its RSS source, or adopts the existing pair for a feed already known; `id` addresses an existing show. The add/remove arguments attach or detach the calling user's personal workspace or an organisation, and `automatedReporting` toggles feed monitoring for the workspace named alongside it. A permission refusal raises; an unreadable or unsafe feed URL comes back as `ok: false` with `error` set. """ series(addOrganisationId: UUID, addUser: Boolean, automatedReporting: Boolean, automatedReportingOrganisationId: UUID, color: String, feedUrl: String, id: UUID, organisationId: UUID, removeOrganisationId: UUID, removeUser: Boolean, title: String): SeriesMutation """ Per-show report-email routing for a workspace (plan §5.4): attach members with a role, detach them, or set the caller's own per-show mute. Deliberately separate from `series`: the roster is its own concern and that mutation is already an eleven-argument if/elif chain. """ seriesNotifications(organisationId: UUID!, removeUserIds: [UUID], seriesId: UUID!, setMembers: [SeriesMemberInput], setMyEmailReports: Boolean): SeriesNotifications """ Add a show to a workspace, adopt one another customer already tracks, or edit its settings. `feedUrl` creates the series and its RSS source, or adopts the existing pair for a feed already known; `id` addresses an existing show. The add/remove arguments attach or detach the calling user's personal workspace or an organisation, and `automatedReporting` toggles feed monitoring for the workspace named alongside it. A permission refusal raises; an unreadable or unsafe feed URL comes back as `ok: false` with `error` set. """ podcast(addOrganisationId: UUID, addUser: Boolean, automatedReporting: Boolean, automatedReportingOrganisationId: UUID, color: String, feedUrl: String, id: UUID, organisationId: UUID, removeOrganisationId: UUID, removeUser: Boolean, title: String): SeriesMutation @deprecated(reason: "Use `series`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") """ The deprecated `podcastNotifications` alias of `seriesNotifications`: the same roster write, addressed by `podcastId` and taking `[PodcastMemberInput]`. Its payload is `SeriesNotifications`. """ podcastNotifications(organisationId: UUID!, podcastId: UUID!, removeUserIds: [UUID], setMembers: [PodcastMemberInput], setMyEmailReports: Boolean): SeriesNotifications @deprecated(reason: "Use `seriesNotifications`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") register(captchaToken: String!, email: String!, firstName: String!, id: UUID, inviteToken: String, lastName: String!, password: String!): Register lookupUser(email: String, id: UUID): LookupUser changePlan(organisationId: UUID, priceId: String!): ChangePlanMutation cancelPlan(organisationId: UUID): CancelPlanMutation setPaddleCustomerId(currency: String, organisationId: UUID, paddleCustomerId: String!): SetPaddleCustomerIdMutation tokenAuth(email: String!, password: String!): CustomObtainJSONWebToken verifyToken(token: String): Verify refreshToken(refreshToken: String): Refresh organisation(id: UUID, name: String): OrganisationMutation organisationDelete(organisationId: UUID!): OrganisationDeleteMutation user(email: String, emailNotificationBilling: Boolean, emailNotificationProductAnnouncements: Boolean, emailNotificationPromotions: Boolean, emailNotificationReportCreationFeed: Boolean, emailNotificationReportCreationMe: Boolean, emailNotificationReportCreationOthers: Boolean, firstName: String, id: UUID!, lastName: String): UserMutation userInvite( email: String! firstName: String lastName: String organisationId: UUID! podcasts: [PodcastAssignmentInput] @deprecated(reason: "Use `series`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") role: String sendReportEmails: Boolean """Shows to attach the invitee to, each with their per-show role.""" series: [SeriesAssignmentInput] ): UserInviteMutation userUninvite(organisationId: UUID!, userId: UUID!): UserUninviteMutation userEmailVerify(email: String!, token: String!): UserEmaiVerifyMutation leaveOrganisation(organisationId: UUID!): LeaveOrganisationMutation setOrganisationRole(organisationId: UUID!, role: String!, userId: UUID!): SetOrganisationRoleMutation """ The org-scoped overrides on one member's row — `org.report`/`org.enhance` (plan §5.4, §9 decision 19) and `org.api_keys` (REST plan §4.13, which no role preset but Owner's grants, and which only an Owner may hand out). Podcast-shaped extras travel through `podcastNotifications` instead; the save-time validator polices scope, vocabulary and redundancy (an Admin+ target's preset already grants the spend pair, so writing it there fails naturally). """ setOrganisationMemberPermissions(extraPermissions: [String]!, organisationId: UUID!, userId: UUID!): SetOrganisationMemberPermissionsMutation getUpdatePaymentMethodTransaction(subscriptionId: String!): GetUpdatePaymentMethodTransaction sendMagicLink(email: String!): SendMagicLink verifyMagicLink(email: String!, magicToken: String!): VerifyMagicLink createApiKey(description: String!, organisationId: UUID): CreateApiKeyMutation updateApiKey(description: String, enabled: Boolean, id: UUID!): UpdateApiKeyMutation deleteApiKey(id: UUID!): DeleteApiKeyMutation } """ The "How did you hear about us?" answer collected during onboarding. Lands on the row registration created, but get-or-creates so an account that predates attribution capture can still answer. """ type AttributionSelfReport { ok: Boolean error: String } """ Returns the Paddle one-time checkout parameters for a credit pack. The actual credit grant happens later, from the verified transaction.completed webhook. """ type PurchaseCreditPack { ok: Boolean error: String checkout: String } """Create or update a customer-owned custom report template.""" type SaveReportTemplate { ok: Boolean error: String template: ReportTemplateType } type LeaveFeedback { ok: Boolean error: String } type GetUploadUrl { ok: Boolean error: String reportId: UUID url: String } type StartReport { ok: Boolean error: String status: String estimatedCredits: Int reportId: UUID } type ReportFromFeed { ok: Boolean reportId: UUID error: String status: String } type RetryReport { ok: Boolean error: String } """ Mint an Enhancement id and a signed PUT URL for its input (and, when metadata+cover is coming, the cover source). Mirrors GetUploadUrl. """ type GetEnhancementUploadUrl { ok: Boolean error: String enhancementId: UUID url: String coverUrl: String } """ Probe a file the browser has just uploaded, before any job is started: how long it is and what chapters it already carries (plan §4.5). A mutation rather than a query because it does real work on a named blob, and the dashboard calls it exactly once per upload. """ type EnhancementFileInfo { ok: Boolean error: String """ Length of the uploaded audio in milliseconds. Null unless the probe succeeded. """ durationMs: Int """ JSON: chapters already embedded in the uploaded file, as `title`/`start` objects with starts in milliseconds. An empty list when it carries none. """ chapters: String } """ Create and queue a combined enhancement job from an uploaded file or a completed report's audio (server-side blob copy — no re-upload). """ type StartEnhancement { ok: Boolean error: String status: String enhancementId: UUID estimatedCredits: Int } """ Propose chapters for a completed report from the transcript it already has. Synchronous by design (plan §2): the transcript exists, so the only wait is the model's, bounded by the read timeout in ``chapters.TIMEOUT``. Charged when — and only when — proposals land, so a failed run costs nothing and leaves no row. """ type GenerateReportChapters { ok: Boolean error: String """ JSON: the proposed chapters as `title`/`start` objects, starts in milliseconds. Null unless the run succeeded. """ chapters: String """ The generation record this run created, and the anchor its charge is attached to. Null unless the run succeeded. """ generationId: UUID """Credits debited for this generation. Null unless the run succeeded.""" creditsCharged: Int } """ Finish the review step of an enhancement paused at ``awaiting_review`` (plan §4.5): write the chapters the user settled on into the job's parameters and let it run, or abandon the job. The generation has already been charged — it was billed the moment the proposals landed — so neither answer here costs anything on its own. Accepting lets the job reach ``complete``, which charges the file write; cancelling never does, because that charge only ever fires on the flip to ``complete``. """ type AcceptEnhancementChapters { ok: Boolean error: String """ The job's status after this call: `pending` when the chapters were accepted and it is queued to run, `failed` when it was cancelled. """ status: String } """ Add a show to a workspace, adopt one another customer already tracks, or edit its settings. `feedUrl` creates the series and its RSS source, or adopts the existing pair for a feed already known; `id` addresses an existing show. The add/remove arguments attach or detach the calling user's personal workspace or an organisation, and `automatedReporting` toggles feed monitoring for the workspace named alongside it. A permission refusal raises; an unreadable or unsafe feed URL comes back as `ok: false` with `error` set. """ type SeriesMutation { ok: Boolean id: UUID error: String } """ Per-show report-email routing for a workspace (plan §5.4): attach members with a role, detach them, or set the caller's own per-show mute. Deliberately separate from `series`: the roster is its own concern and that mutation is already an eleven-argument if/elif chain. """ type SeriesNotifications { ok: Boolean error: String """ The show whose roster was written, answering for the workspace named in the call. """ series: SeriesType podcast: SeriesType @deprecated(reason: "Use `SeriesNotifications.series`. Podcast-named fields are deprecated aliases kept working for existing integrations; a series is a show being audited, a podcast today.") } """ One workspace member to attach to a show, with their per-show role and overrides. """ input SeriesMemberInput { """A member of the organisation.""" userId: UUID! """ `manager` or `viewer`. A new attachment without one lands on viewer; omitted on an existing one leaves the role alone. """ role: String """ Capabilities granted on top of the role's preset. Omitted leaves the existing overrides alone; an empty list clears them. """ extraPermissions: [String] } """ The podcast-named twin of `SeriesMemberInput`, taken by the deprecated `podcastNotifications` alias. Same fields — kept so documents that declare `[PodcastMemberInput]` variables keep validating. """ input PodcastMemberInput { """A member of the organisation.""" userId: UUID! """ `manager` or `viewer`. A new attachment without one lands on viewer; omitted on an existing one leaves the role alone. """ role: String """ Capabilities granted on top of the role's preset. Omitted leaves the existing overrides alone; an empty list clears them. """ extraPermissions: [String] } type Register { ok: Boolean error: String user: UserType } type LookupUser { ok: Boolean error: String userId: String } type ChangePlanMutation { ok: Boolean error: String } type CancelPlanMutation { ok: Boolean } type SetPaddleCustomerIdMutation { ok: Boolean } type CustomObtainJSONWebToken { payload: GenericScalar! refreshExpiresIn: Int! token: String! refreshToken: String! } """ The `GenericScalar` scalar type represents a generic GraphQL scalar value that could be: String, Boolean, Int, Float, List or Object. """ scalar GenericScalar type Verify { payload: GenericScalar! } type Refresh { payload: GenericScalar! refreshExpiresIn: Int! token: String! refreshToken: String! } type OrganisationMutation { ok: Boolean id: UUID error: String } type OrganisationDeleteMutation { ok: Boolean error: String } type UserMutation { ok: Boolean id: UUID error: String } type UserInviteMutation { ok: Boolean id: UUID error: String } """ The podcast-named twin of `SeriesAssignmentInput`, taken by the deprecated `userInvite(podcasts:)` argument. Kept so documents that declare `[PodcastAssignmentInput]` variables keep validating. """ input PodcastAssignmentInput { """A show in the inviting organisation.""" podcastId: UUID! """`manager` or `viewer`; viewer when omitted.""" role: String } """One show to attach an invitee to, with their per-show role.""" input SeriesAssignmentInput { """A show in the inviting organisation.""" seriesId: UUID! """`manager` or `viewer`; viewer when omitted.""" role: String } type UserUninviteMutation { ok: Boolean error: String } type UserEmaiVerifyMutation { ok: Boolean } type LeaveOrganisationMutation { ok: Boolean } type SetOrganisationRoleMutation { ok: Boolean error: String } """ The org-scoped overrides on one member's row — `org.report`/`org.enhance` (plan §5.4, §9 decision 19) and `org.api_keys` (REST plan §4.13, which no role preset but Owner's grants, and which only an Owner may hand out). Podcast-shaped extras travel through `podcastNotifications` instead; the save-time validator polices scope, vocabulary and redundancy (an Admin+ target's preset already grants the spend pair, so writing it there fails naturally). """ type SetOrganisationMemberPermissionsMutation { ok: Boolean error: String } type GetUpdatePaymentMethodTransaction { ok: Boolean id: String } type SendMagicLink { ok: Boolean error: String } type VerifyMagicLink { ok: Boolean error: String token: String refreshToken: String refreshExpiresIn: Int payload: GenericScalar user: UserType } type CreateApiKeyMutation { ok: Boolean apiKey: ApiKeyWithKeyType error: String key: String } """ An API key together with its full secret `key`, returned only by the `createApiKey` mutation — everywhere else a key appears, only the masked form is published. Capture the value at creation; a lost key has to be replaced, not recovered. """ type ApiKeyWithKeyType { id: UUID description: String enabled: Boolean key: String owner: OwnerUnion } type UpdateApiKeyMutation { ok: Boolean apiKey: ApiKeyType error: String } type DeleteApiKeyMutation { ok: Boolean error: String }