Usage and billing
Everything here is scoped to your organization, not to a single catalog. An organization may own several catalogs — a production one and a sandbox, say — and receives one bill covering all of them. Usage is still measured per catalog, so the figures below are totals across the catalogs you own.
Two permissions, not one. usage needs only
usage_read and returns quantities — bytes held, bytes delivered. The
money-bearing fields need billing_read, which also discloses your
negotiated rates. Issue a key with usage_read alone to chart delivery
volume in your own monitoring without exposing your commercial terms.
Figures are up to the minute. Finished days come from a daily
sample; today is measured when you ask, so a delivery counts as soon as
it lands. asOf says when, and observedDays how many complete days the
forecast rests on.
Trials. A new organization starts on a 7-day trial (trial). Canceling (cancelTrial) means nothing is charged; the SFTP endpoint is turned off at once, the console and API keep working until the trial's end, and then the organization and its data are deleted. You can resume until then. Two days before the trial ends we email the owner the date and amount. To start paying before the trial ends, start your subscription. Only a signed-in owner or admin can do any of these — never an API key.
A trial may deliver 50 GB. An upload that would go past it is refused
with extensions.code TRIAL_LIMIT, plus limitBytes and usedBytes;
your SFTP endpoint stops accepting files at the same point, and anything
already sent is kept and processed once the subscription starts.
A projection is not a bill. billingPeriod forecasts where the period is heading; your invoice is computed from frozen figures once the period closes. They are produced by the same pricing logic, so they agree — but only the invoice is final.
Amounts are integers. Money is in minor units (cents), and rates and
quantities are scaled by 106, so 0.12 is 120000 and
1.5 GB is 1500000. No field here is a float.
- querytrial
- mutationcancelTrial
- mutationresumeTrial
- mutationstartSubscription
- querybillingContact
- mutationupdateBillingContact
- mutationcreateBillingPortalSession
- queryusage
- querybillingPeriod
- querypriceTerms
- queryinvoices
The Usage object#
Quantities for one period, across every catalog your organization owns. No rates and no amounts.
Attributes
periodStartString!First day of the period.periodEndString!The day after the period ends.2026-10-01means September.bytesHeldInt!Average bytes of the files you delivered that your catalogs held across the period so far, today included. A catalog's size persists day to day, so this is an average of snapshots, not a total of deliveries. Storage overage is priced on this plusbytesEncodedHeld.bytesHeldNowInt!What your catalogs hold right now. Differs frombytesHeldwhenever your catalog grew or shrank this period.bytesEncodedHeldInt!Average bytes of the encodings //SCHEMA made of your masters — the AAC streams and preview clips, FLAC, MP3s you requested — over the same days. They are stored on your behalf, so storage overage is priced onbytesHeldplus this.bytesEncodedHeldNowInt!The encodings held right now.bytesIngestedInt!Bytes delivered during the period, up toasOf. What you sent, before deduplication — the number you can check against your own records.observedDaysInt!Complete days counted so far. Today is included in the figures above, measured live, but is not a complete day.periodDaysInt!Days in the whole period.asOfDateTime!When the live figures were measured — normally the moment you asked.
{
"periodStart": "2026-09-01",
"periodEnd": "2026-10-01",
"bytesHeld": 1482000000000,
"bytesHeldNow": 1503000000000,
"bytesEncodedHeld": 401000000000,
"bytesEncodedHeldNow": 406000000000,
"bytesIngested": 94000000000,
"observedDays": 21,
"periodDays": 30,
"asOf": "2026-09-22T14:05:31Z"
}The BillingPeriod object#
What has accrued this period and what it is heading for, in the same lines your invoice will carry.
Attributes
periodStartString!First day of the period.periodEndString!The day after the period ends.currencyString!ISO 4217 code for every amount on this object.toDateCentsInt!Accrued so far, in minor units, up toasOf— today's deliveries count as soon as they land. The monthly fee accrues a day at a time, each day's share once the day has begun, rather than appearing in full on the first.projectedCentsInt!Where the period is heading, in minor units: observed usage extended to the end of the period.projectedLowCentsInt!Low end of a plausible range around the projection.projectedHighCentsInt!High end. Wider when your delivery volume has been uneven, since that is the part hardest to predict. Equal to the low end once the period is complete.confidenceString!One sentence naming how the forecast was made, to show beside the number.isFinalBoolean!trueonce every day of the period has been counted. Even then this is not your invoice — see invoices.asOfDateTime!When today's share was measured.toDateCentsis as of this instant.lines[BillingLine!]!The projected charges, broken out. See BillingLine.
{
"periodStart": "2026-09-01",
"periodEnd": "2026-10-01",
"currency": "USD",
"toDateCents": 24430,
"projectedCents": 35900,
"projectedLowCents": 35900,
"projectedHighCents": 35900,
"confidence": "Up to the minute. Forecast from 21 of 30 days, plus today so far.",
"isFinal": false,
"asOf": "2026-09-22T14:05:31Z"
}The BillingLine object#
One charge, in the form it will take on your invoice.
Attributes
kindString!What sort of charge:platform_fee,storage_overage,ingest,migration,creditoradjustment.descriptionString!The line as it reads on the invoice.quantityMicrosInt!How much, scaled by 106.1.5is1500000.unitString!WhatquantityMicroscounts:month,GB-month,GBoreach.unitPriceMicrosInt!Price per unit, scaled by 106.$0.12is120000.amountCentsInt!The charge, in minor units. Negative for a credit.
{
"kind": "storage_overage",
"description": "Catalog storage — 1482 GB average, 1000 GB included",
"quantityMicros": 482000000,
"unit": "GB-month",
"unitPriceMicros": 120000,
"amountCents": 6000
}The PriceTerms object#
The pricing in force today, and when it next changes.
Attributes
effectiveFromString!When these terms started.platformFeeMicrosInt!Fixed platform fee perfeeInterval, scaled by 106.$299a month is299000000;$2,999a year is2999000000.feeIntervalString!monthoryear: how often the platform fee recurs.feeTimingString!advancewhen the platform fee is charged at the start of the period it covers, as on plans started online;arrearswhen it is invoiced after the period. Storage overage and delivery are always invoiced after.includedGbInt!Catalog storage included in the platform fee, in GB.overageMicrosGbMonthInt!Price per GB-month beyondincludedGb, scaled by 106.ingestMicrosGbInt!Price per GB delivered, scaled by 106.0when delivery is not charged separately.- API requests included per month.
nullmeans requests are not limited. - The date your terms change, if a new agreement has been scheduled.
nullif nothing is scheduled.
{
"effectiveFrom": "2026-01-01",
"platformFeeMicros": 299000000,
"feeInterval": "month",
"feeTiming": "arrears",
"includedGb": 1000,
"overageMicrosGbMonth": 120000,
"ingestMicrosGb": 0,
"includedRequests": null,
"nextChangeOn": "2026-10-01"
}The InvoiceSummary object#
One issued invoice.
Attributes
invoicePkInt!The invoice's id.periodStartString!First day of the period billed.periodEndString!The day after the period billed ends.statusString!openawaiting payment,paid,void, oruncollectible.currencyString!ISO 4217 code.totalCentsInt!The amount due, in minor units.- When it was issued.
- When payment is due.
{
"invoicePk": 311,
"periodStart": "2026-08-01",
"periodEnd": "2026-09-01",
"status": "paid",
"currency": "USD",
"totalCents": 35900,
"issuedAt": "2026-09-01T02:14:07.881002+00:00",
"dueAt": "2026-10-01T02:14:07.881002+00:00"
}The BillingContact object#
Where your invoices are sent, and whether a payment method can be managed yet.
Attributes
legalNameString!The entity invoices are issued to. Agreed with //SCHEMA, not editable here.billingEmailString!Where invoices are emailed.- Free text, one field. International address formats do not survive being split into street/city/state, so //SCHEMA does not try.
currencyString!ISO 4217. Fixed when your account is set up — past invoices do not convert, so it cannot change later.paymentTermsDaysInt!Net terms.0means due on receipt. A contract term, not a setting.paymentReadyBoolean!truewhen a payment method can be managed.falsemeans //SCHEMA has not finished setting up your billing account — createBillingPortalSession will error until it is.
{
"legalName": "Halcyon Audio Ltd",
"billingEmail": "[email protected]",
"postalAddress": "12 Wharf Road, London N1 7GR",
"currency": "USD",
"paymentTermsDays": 30,
"paymentReady": true
}The Trial object#
A new organization's 7-day trial.
Attributes
- When the trial began.
endsAtDateTime!When the first charge falls due — or, once canceled, when access ends and your organization's data is deleted.- When the trial was canceled, or
null. Once set, nothing will be charged. - When an owner or admin asked to start the subscription early, or
null. The charge follows within seconds; until it succeeds the trial continues. mayChangeBoolean!Whether you, as the caller, may cancel or resume:trueonly for a signed-in owner or admin.
{
"startedAt": "2026-10-04T21:41:00Z",
"endsAt": "2026-10-11T21:41:00Z",
"cancelledAt": null,
"conversionRequestedAt": null,
"mayChange": true
}Get your trial#
Your organization's trial: when it ends and whether it has been
canceled. null when your organization is not in a trial.
Arguments
None.
Returns
A Trial object, or null.
query {
trial {
endsAt
cancelledAt
mayChange
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n trial {\n endsAt\n cancelledAt\n mayChange\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
trial {
endsAt
cancelledAt
mayChange
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"trial": {
"endsAt": "2026-10-11T21:41:00Z",
"cancelledAt": null,
"mayChange": true
}
}
}Cancel your trial#
Cancel your trial. Nothing is charged. Your SFTP endpoint is turned
off now; the console and API keep working until endsAt, when your
organization and everything delivered to it are deleted. A
confirmation is emailed to you. You can
resume any time before then.
Requires a signed-in owner or admin — an API key is refused whatever its scopes, so a leaked integration key cannot end your account. Canceling a canceled trial returns it unchanged.
Arguments
reasonStringOptional. Why you are canceling — it helps us, and is kept with the cancellation.
Returns
The Trial, now with cancelledAt set.
mutation {
cancelTrial(reason: "Evaluated; not the right fit yet") {
endsAt
cancelledAt
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n cancelTrial(reason: \"Evaluated; not the right fit yet\") {\n endsAt\n cancelledAt\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
cancelTrial(reason: "Evaluated; not the right fit yet") {
endsAt
cancelledAt
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"cancelTrial": {
"endsAt": "2026-10-11T21:41:00Z",
"cancelledAt": "2026-10-06T09:12:44Z"
}
}
}Resume your trial#
Undo a cancellation, any time before the trial ends. The trial keeps its original end date and your SFTP endpoint is set up again — on a new IP address, since the previous one was released. Resuming a trial that is not canceled returns it unchanged.
Arguments
None.
Returns
The Trial, with cancelledAt cleared.
mutation {
resumeTrial {
endsAt
cancelledAt
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n resumeTrial {\n endsAt\n cancelledAt\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
resumeTrial {
endsAt
cancelledAt
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"resumeTrial": {
"endsAt": "2026-10-11T21:41:00Z",
"cancelledAt": null
}
}
}Start your subscription now#
End your trial early and start paying: the card you saved at signup
is charged today, for the plan you chose, instead of on endsAt.
The charge follows within seconds — until it succeeds your
organization is still in its trial, and conversionRequestedAt
shows the request is pending. If the card is declined, your
organization is suspended.
Refused for a canceled trial (TRIAL_CANCELLED):
resume it first. Requires a signed-in
owner or admin. Asking twice is one request.
Arguments
None.
Returns
The Trial, with conversionRequestedAt set.
mutation {
startSubscription {
endsAt
conversionRequestedAt
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n startSubscription {\n endsAt\n conversionRequestedAt\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
startSubscription {
endsAt
conversionRequestedAt
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"startSubscription": {
"endsAt": "2026-10-11T21:41:00Z",
"conversionRequestedAt": "2026-10-08T15:02:11Z"
}
}
}Get billing contact#
Where your invoices are sent, and whether a payment method can be
managed yet. Returns null if //SCHEMA has not finished setting up
your billing account.
Arguments
None.
Returns
A BillingContact object, or null.
query {
billingContact {
legalName
billingEmail
postalAddress
currency
paymentTermsDays
paymentReady
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n billingContact {\n legalName\n billingEmail\n postalAddress\n currency\n paymentTermsDays\n paymentReady\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
billingContact {
legalName
billingEmail
postalAddress
currency
paymentTermsDays
paymentReady
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"billingContact": {
"legalName": "Halcyon Audio Ltd",
"billingEmail": "[email protected]",
"postalAddress": "12 Wharf Road, London N1 7GR",
"currency": "USD",
"paymentTermsDays": 30,
"paymentReady": true
}
}
}Update billing contact#
Change where invoices are sent.
This is the whole of billing_write. Currency, payment terms and the
legal entity are contract terms — they are agreed with //SCHEMA and
no API call changes them. No credential can change what you are
charged.
Arguments
- Where invoices are emailed.
postalAddressStringFree text, one field. Omit to clear it.
Returns
The updated BillingContact.
mutation {
updateBillingContact(
billingEmail: "[email protected]"
postalAddress: "12 Wharf Road, London N1 7GR"
) {
billingEmail
postalAddress
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n updateBillingContact(\n billingEmail: \"[email protected]\"\n postalAddress: \"12 Wharf Road, London N1 7GR\"\n ) {\n billingEmail\n postalAddress\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
updateBillingContact(
billingEmail: "[email protected]"
postalAddress: "12 Wharf Road, London N1 7GR"
) {
billingEmail
postalAddress
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"updateBillingContact": {
"billingEmail": "[email protected]",
"postalAddress": "12 Wharf Road, London N1 7GR"
}
}
}Open the billing portal#
Returns a URL to a secure Stripe-hosted page where you can add or change a payment method and download past invoices. Send the customer there; they return to your console when finished.
The link is single-use and short-lived, so mint one per visit rather than storing it. Card details are entered on Stripe's page and never pass through //SCHEMA.
Errors if your organization has no payment account set up yet — check billingContact.paymentReady first.
Arguments
None.
Returns
A URL, as a String.
mutation {
createBillingPortalSession
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n createBillingPortalSession\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
createBillingPortalSession
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"createBillingPortalSession": "https://billing.stripe.com/p/session/test_YWNjdF8xTTJKVG..."
}
}Get current usage#
Quantities for the period in progress, summed across every catalog your organization owns. No rates and no amounts — see billingPeriod for what it will cost.
Arguments
None.
Returns
A Usage object.
query {
usage {
periodStart
periodEnd
bytesHeld
bytesIngested
observedDays
periodDays
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n usage {\n periodStart\n periodEnd\n bytesHeld\n bytesIngested\n observedDays\n periodDays\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
usage {
periodStart
periodEnd
bytesHeld
bytesIngested
observedDays
periodDays
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"usage": {
"periodStart": "2026-09-01",
"periodEnd": "2026-10-01",
"bytesHeld": 1482000000000,
"bytesIngested": 94000000000,
"observedDays": 21,
"periodDays": 30
}
}
}Get spend and forecast#
What has accrued so far this period and what it is heading for, broken down into the same lines your invoice will carry.
toDateCents counts only the days that have happened: a monthly
platform fee accrues daily rather than appearing in full on the
first. projectedCents extends what has been observed to the end of
the period — storage held at its current size, deliveries continuing
at the rate they have been arriving.
Arguments
None.
Returns
A BillingPeriod object.
query {
billingPeriod {
periodStart
toDateCents
projectedCents
projectedLowCents
projectedHighCents
confidence
isFinal
lines { kind description quantityMicros unit unitPriceMicros amountCents }
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n billingPeriod {\n periodStart\n toDateCents\n projectedCents\n projectedLowCents\n projectedHighCents\n confidence\n isFinal\n lines { kind description quantityMicros unit unitPriceMicros amountCents }\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
billingPeriod {
periodStart
toDateCents
projectedCents
projectedLowCents
projectedHighCents
confidence
isFinal
lines { kind description quantityMicros unit unitPriceMicros amountCents }
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"billingPeriod": {
"periodStart": "2026-09-01",
"toDateCents": 24430,
"projectedCents": 35900,
"projectedLowCents": 35900,
"projectedHighCents": 35900,
"confidence": "Forecast from 21 of 30 days observed.",
"isFinal": false,
"lines": [
{
"kind": "platform_fee",
"description": "Platform fee",
"quantityMicros": 1000000,
"unit": "month",
"unitPriceMicros": 299000000,
"amountCents": 29900
},
{
"kind": "storage_overage",
"description": "Catalog storage — 1482 GB average, 1000 GB included",
"quantityMicros": 482000000,
"unit": "GB-month",
"unitPriceMicros": 120000,
"amountCents": 6000
}
]
}
}
}Get your price terms#
The terms in force today, and the date they next change if a new
agreement has been scheduled. Returns null if your organization has
no pricing set — usage is still measured, but nothing is billed.
Read-only. Pricing is agreed with //SCHEMA, so no API call changes what you are charged.
Arguments
None.
Returns
A PriceTerms object, or null.
query {
priceTerms {
effectiveFrom
platformFeeMicros
includedGb
overageMicrosGbMonth
ingestMicrosGb
includedRequests
nextChangeOn
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n priceTerms {\n effectiveFrom\n platformFeeMicros\n includedGb\n overageMicrosGbMonth\n ingestMicrosGb\n includedRequests\n nextChangeOn\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
priceTerms {
effectiveFrom
platformFeeMicros
includedGb
overageMicrosGbMonth
ingestMicrosGb
includedRequests
nextChangeOn
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"priceTerms": {
"effectiveFrom": "2026-01-01",
"platformFeeMicros": 299000000,
"includedGb": 1000,
"overageMicrosGbMonth": 120000,
"ingestMicrosGb": 0,
"includedRequests": null,
"nextChangeOn": "2026-10-01"
}
}
}List invoices#
Issued invoices, newest period first. Invoices still being prepared are not returned — an invoice appears here once it has been issued.
Arguments
limitIntHow many to return. Defaults to 12, capped at 36.
Returns
A list of InvoiceSummary objects.
query {
invoices(limit: 3) {
invoicePk
periodStart
periodEnd
status
currency
totalCents
issuedAt
dueAt
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n invoices(limit: 3) {\n invoicePk\n periodStart\n periodEnd\n status\n currency\n totalCents\n issuedAt\n dueAt\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
invoices(limit: 3) {
invoicePk
periodStart
periodEnd
status
currency
totalCents
issuedAt
dueAt
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"invoices": [
{
"invoicePk": 311,
"periodStart": "2026-08-01",
"periodEnd": "2026-09-01",
"status": "paid",
"currency": "USD",
"totalCents": 35900,
"issuedAt": "2026-09-01T02:14:07.881002+00:00",
"dueAt": "2026-10-01T02:14:07.881002+00:00"
}
]
}
}