Melodreams Melodreams Melodreams Developer API api-dev1.melodreams.com

Melodreams Developer API

A read-only HTTP API for Melodreams profile data and your own analytics. Six GET endpoints, three scopes, one response envelope, and a fixed set of error codes you can switch on.

Introduction #

The Melodreams Developer API reads public profiles and your own account data over HTTPS. The base URL is https://api-dev1.melodreams.com.

Base URL
https://api-dev1.melodreams.com

Every endpoint is a GET and returns JSON. Every request except /health and /v1/scopes needs a bearer token from an account on an active paid plan. The API is server-side only and rejects browser origins by CORS, so hold the token on a server you control. Create a token in the developer portal.

Authentication #

Send a bearer token in the Authorization header on every request. This is the only transport. There is no query-parameter or cookie auth.

cURL
curl --silent \
     --header "Authorization: Bearer ${MELODREAMS_API_TOKEN}" \
     --header "Accept: application/json" \
     "https://api-dev1.melodreams.com/v1/me"

A request with no header returns 401 missing_token and a WWW-Authenticate: Bearer challenge. ?token= and ?api_key= do nothing: a credential in a URL leaks into access logs, shell history, and Referer headers.

Server-side only. A token in browser code is a leaked token.

CORS allows one browser origin, the developer portal, so a fetch() from your own site fails by design. Anything shipped to a browser or an app binary is readable by whoever receives it. Keep the token on a server, put a thin backend of your own in front, and expose only the data your client needs.

An active paid plan is required.

Plan status is checked live on every request against the account that owns the token, not baked in when the token is created. With no active plan, every /v1 request, including /v1/me, returns 402 plan_required. Resubscribing takes effect within seconds and the token itself does not change.

Token format #

A token is one opaque string of 75 characters, built from four parts. The parts are shown so you can recognise a token and tell two of your own apart. Your code should never take it apart. The example below is not a working credential.

mdr_live_Zk9xQ2pM7vTb1sYwLd4NrA_8pQvK2mN4rT7wXyZ0aBcDeFgHiJkLmNoPqRsTuVwXyz
  1. mdrprefix · 3 chars

    Vendor prefix, constant on every Melodreams token. It makes a leaked token greppable in a repository scan and lets secret scanners match the pattern.

  2. liveenvironment · 4 chars

    Environment label, always live. There is no test environment and no mdr_test_ counterpart. For development, mint a separate token with a short expiry and the narrowest scopes.

  3. Zk9xQ2pM7vTb1sYwLd4NrAtoken id · 22 chars

    The public half, base64url. Shown in the portal and safe to log. /v1/me returns it as token.id.

  4. 8pQv…VwXyzsecret · 43 chars

    The secret half, base64url. Shown once at creation and never again. Store it in a secret manager. Nobody at Melodreams can recover it.

Treat the token as opaque. Do not split it on _.

The id and the secret are base64url, and that alphabet includes _ and -, so most tokens contain a _ inside a segment. Splitting on _ and expecting four parts is wrong. Read the whole string from your environment, put it in the header, and never parse, trim, lowercase, or re-encode it. For a short log label, take the first 31 characters, which are mdr_live_<token_id>.

Get a token #

  1. Sign in to a Melodreams account on an active paid plan.
  2. Open the developer portal. The session cookie is shared, so there is no second sign-in.
  3. Go to token management and create a token: give it a name (1 to 60 characters), pick its scopes, and pick an expiry.
  4. Copy the token immediately. The full string is shown once and is never recoverable. If you lose it, revoke it and create another.

Token management is session-authenticated only. An API token cannot create, list, or revoke tokens, including itself. Scopes and expiry are fixed at creation: to change them, mint a new token and revoke the old one. You can hold up to 5 active tokens per account.

Expiry options are 1, 7, 30, 90, or 365 days, or never. Prefer the shortest expiry your deployment can absorb, so rotation is enforced rather than remembered. An expired token returns 401 token_expired.

Making requests #

The cheapest call to confirm a token works is GET /v1/me. It needs a valid token but no scope, and reports the account, scopes, and limits the token carries. Here it is with curl and with the SDK.

cURL
curl --silent \
     --header "Authorization: Bearer ${MELODREAMS_API_TOKEN}" \
     "https://api-dev1.melodreams.com/v1/me"
SDK
import { Melodreams } from '@heymelodreams/sdk';

const mdr = new Melodreams(process.env.MELODREAMS_API_TOKEN);

const me = await mdr.me();
console.log(`Token for @${me.username}, tier ${me.tier}`);

Set the token in your environment first and read it from there. Never hardcode it.

Set the token
export MELODREAMS_API_TOKEN="mdr_live_..."

The same first call with Python and requests:

Python
import os
import requests

BASE = "https://api-dev1.melodreams.com"
token = os.environ["MELODREAMS_API_TOKEN"]

response = requests.get(
    BASE + "/v1/me",
    headers={"Authorization": "Bearer " + token, "Accept": "application/json"},
    timeout=10,
)
response.raise_for_status()
me = response.json()["data"]
print("Token for @{}, tier {}".format(me["username"], me["tier"]))

Response format #

Every /v1 response uses one of two shapes. Success carries data. An error carries a code you switch on. You never branch on which endpoint produced an error.

Success
{
  "ok": true,
  "data": { }
}
Error
{
  "ok": false,
  "error": "insufficient_scope",
  "message": "This endpoint requires the \"analytics:read\" scope. This token has: public:read.",
  "docs": "https://api-dev1.melodreams.com/docs#insufficient_scope"
}
ok
boolean true on success, false on error.
data
object The endpoint payload. Present on success only.
error
string A stable snake_case code. Present on error only. Switch on this.
message
string Human-readable text for logs. May be reworded at any time, and is often more specific than the generic text in the error reference. Never parse it.
docs
string A link to the matching row in the error reference.
One exception: /health

The liveness endpoint is not wrapped in the envelope. It returns its fields at the top level next to ok. It sits outside /v1 because it is not part of the versioned contract.

  • HTTPS only. Plain HTTP is upgraded, never served.
  • Responses are application/json; charset=utf-8 with X-Content-Type-Options: nosniff.
  • Timestamps are ISO 8601 in UTC, for example 2026-07-22T09:58:12.004Z. That includes X-RateLimit-Reset, which is not a Unix epoch.
  • Unknown fields are dropped, not passed through. The profile shapes are a projection of internal storage, so an editor change in the dashboard is not a breaking API change.
  • A known path called with a method other than GET returns 404 not_found, not 405.

Rate limits #

120requests / minute, per token
50,000requests / month, per token
500distinct profiles / day, per token
5active tokens per account

Two request limits apply per token, enforced independently. The burst limit is 120 requests per 60 seconds; exceeding it returns 429 rate_limited and clears within a minute, so retry with backoff. The monthly quota is 50,000 requests per token; exhausting it returns 429 quota_exceeded, which does not clear by retrying.

Limits are keyed on the token id, not on your IP address, so it does not matter how many hosts your integration runs on. The monthly quota resets at 00:00 UTC on the 1st of each calendar month, not on your billing anniversary. The reset instant is returned in X-RateLimit-Reset.

Distinct profiles #

public:read carries a third limit: a token may read at most 500 distinct profiles per UTC day. The 501st new username returns 429 distinct_profile_cap. The word that matters is distinct. Re-reading a username you already fetched today does not count again, so a widget refreshing the same 20 profiles all day touches 20 of your 500. Only breadth is capped, never depth. This limit does not clear by retrying or by trying a different username; it resets at 00:00 UTC.

RequestCounts?Why
GET /v1/profiles/aurora First today A username this token has not read today. Adds one to the day's set. A username that does not exist still counts.
GET /v1/profiles/aurora No Already read today. Unlimited until 00:00 UTC.
GET /v1/me/profile No Your own data.
GET /v1/me/analytics No Your own data.
GET /v1/me No Carries no username.

Headers #

Successful responses, and the two metered 429s (quota_exceeded and distinct_profile_cap), carry your remaining monthly budget, so you can watch consumption without spending a request.

HeaderExampleMeaning
X-RateLimit-Limit 50000 The monthly request quota for this token. Not the per-minute burst limit.
X-RateLimit-Remaining 49713 Requests left this calendar month, after counting this one.
X-RateLimit-Reset 2026-08-01T00:00:00.000Z When the monthly counter resets. An ISO 8601 timestamp, not a Unix epoch.
  • These headers are attached to 2xx responses and to 429 quota_exceeded and 429 distinct_profile_cap. They are not present on 401, 402, 403, 404, or burst 429 rate_limited, which are rejected before metering runs. Read them defensively.
  • The quota is per token, not per account, so a development token cannot spend production's 50,000.

Errors #

Errors use the error envelope. The error field is the contract: a fixed vocabulary of stable snake_case codes. Branch on it and log it. The code and the HTTP status never disagree.

Example error
{
  "ok": false,
  "error": "plan_required",
  "message": "This endpoint requires an active Melodreams subscription.",
  "docs": "https://api-dev1.melodreams.com/docs#plan_required"
}

Retry policy #

  • Retry rate_limited, any 5xx (internal_error, upstream_error, api_unavailable, scope_paused), and network errors, with exponential backoff and jitter and a cap on attempts. For api_unavailable and scope_paused, wait for the Retry-After window.
  • Do not retry quota_exceeded. It clears on the 1st of the month. Alert instead.
  • Do not retry distinct_profile_cap, and do not route around it with a different username. It clears at 00:00 UTC. If you hit it, the shape of the integration is what to change. See Distinct profiles.
  • Never retry any other 4xx. A 401, 402, or 403 needs a human: a new token, a subscription, or a different scope.

Error codes #

StatusCodeWhat it means and what to do
400 bad_request The request was malformed. Not retryable without a change. On token creation this is also how "you already hold the maximum of 5 active tokens" is reported.
400 invalid_username The handle in the path is not a valid Melodreams username. It must match ^[a-z0-9_-]{3,30}$ after lowercasing. Validate before calling.
400 invalid_scope One or more requested scopes are not recognised. Raised when creating a token, not by the read API. Compare against GET /v1/scopes.
401 missing_token No Authorization: Bearer header was sent. ?token= is not a supported alternative. The response carries a WWW-Authenticate: Bearer challenge.
401 malformed_token The credential is not structurally a Melodreams token. Usually a truncated paste or a shell that did not expand your environment variable. Check the string is mdr_live_<22>_<43>, 75 characters. Because _ is legal inside the id and secret, a client that rebuilt the token by splitting on _ lands here.
401 invalid_token Structurally valid, but not a token we accept. An unknown token id and a wrong secret return the same code, so the response cannot probe which ids exist.
401 token_revoked This token was revoked. It will never work again. Mint a new one.
401 token_expired The token passed the expiry it was created with. Not retryable. Mint a replacement.
401 session_required A signed-in Melodreams session is required. Only ever returned by portal token-management routes. You will not see this from /v1.
402 plan_required The token is valid but the owning account has no active subscription. See Authentication. Resubscribing takes effect within seconds.
403 insufficient_scope The token does not carry the scope this endpoint requires. The message names the required scope and lists what the token has. Scopes are immutable. Mint a new token.
403 account_blocked The account behind the token is not in good standing: suspended, locked, or under review. Contact support. There is nothing to fix in your code.
404 not_found No such endpoint. Also returned for a valid path called with a method other than GET. The API does not return 405.
404 profile_not_found No public profile exists for that username. A handle that does not exist and one that is not public are indistinguishable, by design. From /v1/me/profile it means the account has no profile row yet.
410 account_deleted The account behind the token has been deleted. Terminal. Stop retrying and remove the integration.
429 rate_limited Too many requests: the per-token burst limit of 120 per minute. Back off exponentially and retry; it clears within a minute. Also returned when a source IP has exhausted the budget for failed authentication and then sends a request with no token or an unparseable one, in which case the credential is the problem, not the pace. See Rate limits.
429 quota_exceeded The token's monthly quota is exhausted. Retrying will not help. X-RateLimit-Reset and the message both carry the reset instant.
429 distinct_profile_cap This token has read 500 distinct profiles today and asked for a 501st. Re-reading a username it already fetched today still works; only new ones are refused. Resets at 00:00 UTC. Retrying will not help, and neither will a different username. See Distinct profiles.
500 internal_error Something went wrong on our side. No stack traces are included. Retry once with backoff; if it persists, report it with the timestamp.
503 upstream_error A dependency was unavailable or too slow. Transient. Retry shortly with backoff and jitter.
503 api_unavailable We paused the API on purpose, for maintenance or during an incident. The response carries a Retry-After header and usually a short note. Retry after the window. Nothing is wrong with your token.
503 scope_paused One scope is switched off temporarily, usually public:read during a scraping incident. Your other scopes keep working. Retry after the Retry-After window.

Scopes #

Scopes are chosen when a token is created and are fixed for its lifetime. A token that lacks a required scope gets 403 insufficient_scope, and the message names the scope required and the scopes the token has. Scopes are additive and least privilege: grant the narrowest set that works, and mint a new token to widen access.

Every scope in v1 is a read. There is no write scope and no webhook scope, because no code path in the API mutates or exports anything. Scope strings never change meaning: a widened capability ships as a new string, so a token issued under the old meaning cannot silently gain access.

ScopeGrantsReadsPaid feature
public:read Read any public Melodreams profile. Exposes nothing that melo.bio/<username> does not already serve to an anonymous browser. This is the only scope subject to the distinct-profile cap. Anyone's public data No
profile:read Read the token owner's own profile, links, theme, and visibility settings. Owner only No
analytics:read Read the token owner's profile view and link click counters. Owner only Yes

The live catalogue is served by GET /v1/scopes. The paid-feature column marks scopes gated by plan in the product: analytics is not available to free accounts anywhere. That is separate from the plan requirement, which applies to the whole API regardless of scope.

Endpoints #

Six endpoints. All GET. All read-only.

EndpointAuthScope
GET /healthNoneNone
GET /v1/scopesNoneNone
GET /v1/meTokenAny
GET /v1/profiles/{username}Tokenpublic:read
GET /v1/me/profileTokenprofile:read
GET /v1/me/analyticsTokenanalytics:read

GET /health #

GET /health No authentication

Unauthenticated liveness check. Use it for uptime monitoring and to confirm reachability from a new network before you debug credentials. It costs no quota. GET /v1/health is an alias. This is the one endpoint not wrapped in the data envelope.

Example request

cURL
curl --silent "https://api-dev1.melodreams.com/health"

Example response 200

JSON
{
  "ok": true,
  "service": "developer-api",
  "version": "v1",
  "ready": true,
  "mode": "ok",
  "cache": { "entries": 412, "max": 5000 },
  "timestamp": "2026-07-22T10:31:04.118Z"
}

GET /v1/scopes #

GET /v1/scopes No authentication

The machine-readable scope catalogue, exactly as the portal and this page render it. Public and unauthenticated because it is not sensitive, and useful for generating a permissions UI of your own. Costs no quota.

Example request

cURL
curl --silent "https://api-dev1.melodreams.com/v1/scopes"

Example response 200

JSON
{
  "ok": true,
  "data": {
    "scopes": [
      {
        "scope": "public:read",
        "description": "Read any public Melodreams profile.",
        "requires_subscription": false
      },
      {
        "scope": "profile:read",
        "description": "Read the token owner's own profile, links and theme.",
        "requires_subscription": false
      },
      {
        "scope": "analytics:read",
        "description": "Read the token owner's profile view and link click counters.",
        "requires_subscription": true
      }
    ]
  }
}

GET /v1/me #

GET /v1/me Valid token, no scope required

Who the token belongs to, what it can do, and what its limits are. Any valid token may call it, whatever its scopes. Call it at start-up to verify credentials and reach for it first when something is not working.

Response fields

user_id
string (uuid) The account that owns the token.
username
string | null The owner's handle.
org_id
string (uuid) | null Set only for organisation-scoped tokens.
account_type
string user or org.
tier
string The live access tier, for example full or past_due_grace.
subscribed
boolean True while the plan requirement is satisfied.
token.id
string The public token id, segment 3 of the token. Safe to log.
token.environment
string Always live.
token.scopes
string[] Sorted and de-duplicated.
limits
object requests_per_minute and requests_per_month for this token.

Example request

cURL
curl --silent \
     --header "Authorization: Bearer ${MELODREAMS_API_TOKEN}" \
     "https://api-dev1.melodreams.com/v1/me"

Example response 200

JSON
{
  "ok": true,
  "data": {
    "user_id": "8f2b1c44-7d6e-4a19-b3c0-5e91d2a7f004",
    "username": "aurora",
    "org_id": null,
    "account_type": "user",
    "tier": "full",
    "subscribed": true,
    "token": {
      "id": "Zk9xQ2pM7vTb1sYwLd4NrA",
      "environment": "live",
      "scopes": ["analytics:read", "profile:read", "public:read"]
    },
    "limits": {
      "requests_per_minute": 120,
      "requests_per_month": 50000
    }
  }
}

GET /v1/profiles/:username #

GET /v1/profiles/{username} public:read

Any public Melodreams profile by handle. The response is a stable projection of the profile: fields the API does not document are dropped, so a field you see here will still be here next month.

Path parameters

username
string, required Case-insensitive and lowercased before lookup. Must match ^[a-z0-9_-]{3,30}$ once lowercased, otherwise 400 invalid_username.

A handle that does not exist and a handle whose profile is not public both return 404 profile_not_found, so the endpoint cannot be used to enumerate private handles. This is the only endpoint subject to the distinct-profile cap. Responses are cacheable: they carry Cache-Control: public, max-age=60, so an edit can take up to a minute to appear. Honour the header to save quota.

Response fields

username
string
display_name
string | null
bio
string | null
avatar_url, banner_url
string | null
theme
string | null The profile's theme identifier.
avatar
object shape, border, border_color, frame, frame_color.
links[]
array Each entry has title, url, style (big or small), and type (defaults to standard). Links with no destination and hidden links are omitted.
badges
object verified, staff, developer, each a boolean.
flags
object access_code_required, status_dot, discoverable, listable. A gated profile is reported as a boolean only; the access-code hash is never read from storage.
custom_domain
string | null
melo_bio_mode
string Defaults to both.
updated_at
string (ISO 8601) | null

Example request

cURL
curl --silent \
     --header "Authorization: Bearer ${MELODREAMS_API_TOKEN}" \
     "https://api-dev1.melodreams.com/v1/profiles/aurora"

Example response 200

JSON
{
  "ok": true,
  "data": {
    "username": "aurora",
    "display_name": "Aurora",
    "bio": "Producer. Night shift. Mixing in Berlin.",
    "avatar_url": "https://img.melodreams.com/u/aurora/avatar.webp",
    "banner_url": null,
    "theme": "midnight",
    "avatar": {
      "shape": "circle",
      "border": true,
      "border_color": "#3b82f6",
      "frame": "none",
      "frame_color": null
    },
    "links": [
      {
        "title": "Latest release",
        "url": "https://open.spotify.com/album/4kaurora",
        "style": "big",
        "type": "music"
      },
      {
        "title": "Tour dates",
        "url": "https://aurora.example/tour",
        "style": "small",
        "type": "standard"
      }
    ],
    "badges": {
      "verified": true,
      "staff": false,
      "developer": false
    },
    "flags": {
      "access_code_required": false,
      "status_dot": true,
      "discoverable": true,
      "listable": true
    },
    "custom_domain": null,
    "melo_bio_mode": "both",
    "updated_at": "2026-07-19T21:04:33.512Z"
  }
}

GET /v1/me/profile #

GET /v1/me/profile profile:read

The token owner's own profile. Identical in shape to the public projection, with one extra visibility object. Works whether or not the profile is currently public. This response is specific to one token and is sent with Cache-Control: no-store.

Additional fields

visibility.is_public
boolean Whether the profile is served publicly at all.
visibility.custom_domain_redirect
boolean
visibility.reserved_style
string Defaults to hidden.

If the account has no profile row yet, the endpoint returns 404 profile_not_found.

Example request

cURL
curl --silent \
     --header "Authorization: Bearer ${MELODREAMS_API_TOKEN}" \
     "https://api-dev1.melodreams.com/v1/me/profile"

Example response 200, abridged

JSON
{
  "ok": true,
  "data": {
    "username": "aurora",
    "display_name": "Aurora",
    "bio": "Producer. Night shift. Mixing in Berlin.",
    "avatar_url": "https://img.melodreams.com/u/aurora/avatar.webp",
    "banner_url": null,
    "theme": "midnight",
    "avatar": {
      "shape": "circle",
      "border": true,
      "border_color": "#3b82f6",
      "frame": "none",
      "frame_color": null
    },
    "links": [
      {
        "title": "Latest release",
        "url": "https://open.spotify.com/album/4kaurora",
        "style": "big",
        "type": "music"
      }
    ],
    "badges": {
      "verified": true,
      "staff": false,
      "developer": false
    },
    "flags": {
      "access_code_required": false,
      "status_dot": true,
      "discoverable": true,
      "listable": true
    },
    "custom_domain": null,
    "melo_bio_mode": "both",
    "updated_at": "2026-07-19T21:04:33.512Z",
    "visibility": {
      "is_public": true,
      "custom_domain_redirect": false,
      "reserved_style": "hidden"
    }
  }
}

GET /v1/me/analytics #

GET /v1/me/analytics analytics:read Paid feature

The token owner's profile view count and link click counters. No query parameters, no date ranges, no filters: it returns the current counters, the same data the dashboard shows. Analytics is a paid capability, so this endpoint re-checks the live plan on every request in addition to the scope check. A profile with nothing recorded yet returns zeros, not a 404.

Response fields

views
integer Total profile views.
clicks.total
integer Total link clicks.
clicks.by_link[]
array Each entry has link_id, title, url, style, and count, sorted by count descending and capped at the top 100 links.
updated_at
string (ISO 8601) | null When the counters were last written.

Example request

cURL
curl --silent \
     --header "Authorization: Bearer ${MELODREAMS_API_TOKEN}" \
     "https://api-dev1.melodreams.com/v1/me/analytics"

Example response 200

JSON
{
  "ok": true,
  "data": {
    "views": 18422,
    "clicks": {
      "total": 4310,
      "by_link": [
        {
          "link_id": "lnk_9f2a",
          "title": "Latest release",
          "url": "https://open.spotify.com/album/4kaurora",
          "style": "big",
          "count": 2871
        },
        {
          "link_id": "lnk_3c1b",
          "title": "Tour dates",
          "url": "https://aurora.example/tour",
          "style": "small",
          "count": 1102
        }
      ]
    },
    "updated_at": "2026-07-22T09:58:12.004Z"
  }
}

Versioning #

The version is in the path. Everything under /v1 keeps its shape. A breaking change ships as a new path, /v2; /v1 is not altered in place.

These changes are additive and can ship without notice, so parse defensively and ignore fields you do not recognise:

  • New endpoints, new response fields, and new optional query parameters.
  • New scopes. An existing token does not gain them; mint a new token to get them.

These are breaking and ship as /v2:

  • Removing or renaming a field, narrowing a type, or changing the meaning of an existing value.
  • Adding a required parameter or tightening a limit.

Error codes are frozen. A shipped error value never changes meaning; only its message is reworded. Tokens are opaque, so never parse a token to infer a version or anything else.

SDKs and OpenAPI #

The official SDK is TypeScript, has zero dependencies, and is server-side only.

Install
npm i @heymelodreams/sdk

Construct a client with one token. Each method mirrors one endpoint and returns the data payload. Any non-ok response throws a typed MelodreamsError with code and status, so you apply the retry policy by branching on err.code.

SDK
import { Melodreams, MelodreamsError } from '@heymelodreams/sdk';

const mdr = new Melodreams(process.env.MELODREAMS_API_TOKEN);

try {
  const me = await mdr.me();
  console.log(`Token for @${me.username}, tier ${me.tier}`);

  const profile = await mdr.profile('aurora');
  console.log(`${profile.display_name}: ${profile.links.length} links`);

  const stats = await mdr.myAnalytics();
  console.log(`${stats.views} views, ${stats.clicks.total} clicks`);
} catch (err) {
  if (err instanceof MelodreamsError) {
    console.error(`${err.code} (${err.status})`);
    process.exit(1);
  }
  throw err;
}

OpenAPI spec #

The full API is described by an OpenAPI 3.1 document, served from this documentation site. It covers every path, parameter, response schema, error code, and rate-limit header on this page.

Specification
/docs/openapi.json

Save the file, then point any generator at it for a typed client. The first command below emits TypeScript types with no runtime dependency; the second generates a full client in most mainstream languages.

Generate a client
npx openapi-typescript ./openapi.json --output melodreams.d.ts

npx @openapitools/openapi-generator-cli generate \
  --input-spec ./openapi.json \
  --generator-name python \
  --output ./melodreams-client
Generated clients and retries

Most generators produce a client with no retry logic and no distinction between the three 429s: rate_limited, which is retryable, and quota_exceeded and distinct_profile_cap, which are not. Wrap the generated call in a layer that follows the retry policy, or you will hammer a limit that cannot recover until midnight or the 1st of the month.