Get Started
Developer documentation

The Renew Reminder API

A REST API over your renewal records. Import vendors in bulk, keep them in step with a system you already run, and read what is coming up. JSON in, JSON out, authenticated with a bearer key.

Included with the Business plans

The API comes with Business Starter and Business, including the 14-day trial. Its keys belong to a company workspace and are created by that workspace's Admins. Premium and the free plan do not include it. See Renew Reminder for Business.

Getting started

Every request goes to one base URL. All paths below are relative to it.

https://api.renewreminder.com/api/v1

Create a workspace key (see Workspace keys), then read your first page of records:

curl https://api.renewreminder.com/api/v1/records \
  -H "Authorization: Bearer $RR_API_KEY"

Authentication

Send your key as a bearer token. Keys look like rrk_live_... and are shown once at creation. We store only a hash, so a lost key cannot be recovered. To replace a key that is lost or exposed, revoke it in the web app and create a new one.

Authorization: Bearer rrk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Call the API from your own server, never from a web page. A key is a secret that belongs in a server's environment, and the API grants no browser origin CORS access, so a request made from front-end code fails in the browser.

What a key cannot do

An API key reaches the endpoints on this page and nothing else. It cannot sign in, change a password, touch billing, read bank credentials, mint or revoke other keys, or alter your alert settings. That last exclusion is deliberate: a key that could silence reminders would be the quietest way to do damage with one. A key also cannot connect an AI assistant: the Claude & ChatGPT connector signs in with OAuth and refuses API keys.

Endpoints

MethodPathScopeDescription
GET/meprofile:readThe key's workspace, its plan, and the key itself.
GET/recordsrecords:readList records. Filterable and paginated.
GET/records/upcominginsights:readActive records billing soon, soonest first. At most 500.
GET/records/:idrecords:readA single record.
POST/recordsrecords:writeCreate a record.
PATCH/records/:idrecords:writeUpdate the writable fields of a record.
DELETE/records/:idrecords:writeDelete a record.
GET/insights/spendinsights:readSpend normalised to a monthly figure.
GET/categoriescategories:readThe workspace's categories.
POST/categoriescategories:writeCreate a workspace category.

Create a record

curl -X POST https://api.renewreminder.com/api/v1/records \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Figma",
    "amount": 45,
    "currency": "USD",
    "billingCycle": "monthly",
    "nextBillingDate": "2026-09-01",
    "category": "Design"
  }'

Records created this way are stamped source: "api" by the server and land in the key's workspace. You cannot set either from the request body. A new record answers 201; if an identical record already exists, you get that one back with 200 and "deduped": true instead of a second copy.

Check a key

GET /me describes the key you sent: the workspace it belongs to, the plan that entitles it (business_starter or business), and the key's own details. It returns no personal data about the person who created the key, only their user id.

{
  "success": true,
  "workspace": { "id": "8f2c...", "name": "Acme Inc.", "baseCurrency": "USD" },
  "plan": "business_starter",
  "key": {
    "prefix": "rrk_live_x7Q",
    "name": "Finance sync",
    "environment": "live",
    "scopes": ["records:read", "categories:read", "insights:read", "profile:read"],
    "createdBy": "3a91..."
  }
}

The record object

One shape covers subscriptions, bills and investments; the type field tells them apart. Absent values are null, never missing.

{
  "id": "665f1c...",
  "type": "subscription",
  "name": "Figma",
  "description": null,
  "merchant": "Figma Inc.",
  "amount": 45,
  "currency": "USD",
  "originalAmount": null,
  "originalCurrency": null,
  "originalCurrencySymbol": null,
  "billingCycle": "monthly",
  "nextBillingDate": "2026-09-01T00:00:00.000Z",
  "lastBillingDate": "2026-08-01T00:00:00.000Z",
  "trialEndDate": null,
  "billingPhase": "paid",
  "category": "Design",
  "tags": [],
  "notes": null,
  "splitCount": 1,
  "isActive": true,
  "autoRenew": true,
  "source": "api",

  "workspaceId": "8f2c...",
  "ownerUserId": "3a91...",
  "department": "Design",
  "noticePeriodDays": 60,
  "contractEndDate": "2026-12-31T00:00:00.000Z",
  "autoRenews": true,
  "endsOn": null,
  "renewalDecision": {
    "status": "renew",
    "decidedBy": "3a91...",
    "decidedAt": "2026-08-10T11:20:00.000Z",
    "note": "Renegotiated to 40/seat"
  },
  "ownerTag": {
    "name": "Sarah Lee",
    "email": "sarah@example.com",
    "notify": true,
    "optedOutAt": null
  },
  "vendorContacts": [],

  "createdAt": "2026-08-01T09:12:44.000Z",
  "updatedAt": "2026-08-13T07:03:10.000Z"
}

Writable fields. A PATCH accepts name, description, merchant, amount, currency, billingCycle, nextBillingDate, lastBillingDate, trialEndDate, category, tags, notes, splitCount, isActive, autoRenew, ownerUserId, department, noticePeriodDays, contractEndDate, autoRenews, plus renewalDecision with a status and a note; the server stamps who decided and when. A POST accepts the same fields except renewalDecision, plus recordType, billingPhase, icon, color, referenceNumber, isVariableAmount, ownerTag, vendorContacts.

Any other field in the body is ignored, not rejected, and the response lists it in ignoredFields, on a POST as on a PATCH. Check that list: it is how a typo shows up. A PATCH with no field it accepts answers 400 INVALID_REQUEST.

Filtering and pagination

GET /records accepts type, category, active, search, billingCycle, page, limit. type, category, billingCycle and search each take a single value; repeating one answers 400 INVALID_REQUEST. Pages are 25 records by default and 100 at most, and page is capped at 1000: a higher number returns page 1000.

curl "https://api.renewreminder.com/api/v1/records?active=true&billingCycle=yearly&limit=50" \
  -H "Authorization: Bearer $RR_API_KEY"

The records come back in records, with a pagination block:

{
  "success": true,
  "records": [ ... ],
  "pagination": { "page": 1, "limit": 50, "total": 128, "hasMore": true }
}

GET /records/upcoming is not paginated. It takes days (30 by default, 1 to 365) and returns the active records billing within that window, soonest first, at most 500 of them. When more qualify, you get the 500 soonest and the response carries "hasMore": true; page through GET /records for the full list.

{
  "success": true,
  "windowDays": 30,
  "records": [ ... ],
  "hasMore": true
}

Scopes

Each key carries a set of scopes fixed when it is created. New keys are read-only unless you choose otherwise: in the web app, Read only grants records:read, categories:read, insights:read, profile:read, and Read and write adds records:write, categories:write. Choose read and write only for a system that needs to change records.

ScopeGrants
records:readRead the workspace's records.
records:writeCreate, update and delete the workspace's records.
categories:readRead the workspace's categories.
categories:writeCreate workspace categories.
insights:readRead spend insights and upcoming renewals.
profile:readRead the key's workspace (id, name, base currency), its plan, and the key's own name, prefix, environment, scopes and creator.

Workspace keys

Every API key belongs to one workspace on Business Starter or Business, including the 14-day trial. Keys are created in the web app: open the workspace, then Settings → API keys. The workspace's owner and its Admins can create keys, and see and revoke every key in the workspace, whoever created it. The key is shown once, when you create it.

A key reaches its own workspace and nothing else. It reads and writes that workspace's records and categories, never another workspace's and never anyone's personal subscriptions. Records it creates are tagged to the workspace automatically — you cannot set workspaceId from a request body.

The plan and the creator are checked on every call

A key outlives the plan and the job that justified it, so neither is trusted from the day it was created. A change takes effect within a minute:

  • When the workspace's Business plan or trial ends, its keys pause and return API_REQUIRES_UPGRADE. They work again as soon as the workspace has a Business plan; Admins can still see and revoke them meanwhile.
  • When the key's creator leaves the workspace, the key stops and returns API_WORKSPACE_ACCESS_REVOKED. The same happens when the workspace is deleted.
  • When the creator stops being an Admin, the key stops and returns API_WORKSPACE_ROLE_REVOKED.

Offboarding somebody therefore closes their integrations too, without anyone having to remember to revoke them. A revoked key never works again.

Quota and key allowances follow the workspace's plan, not the plan of whoever created the key, and the key allowance is a shared pool across the company rather than per person.

Rate limits and quotas

Two limits apply: a monthly call quota and a per-minute burst. Both are set by the workspace's plan, and both are counted per key, so each key has the full allowance below. The month is the calendar month in UTC. The Keys column is the most live keys one workspace can hold, shared by all its Admins. During the 14-day trial, the limits are those of the plan being trialled.

PlanCalls / monthKeysBurst / min
Business Starter $49/mo100,00025300
Business $149/mo500,0001001000

API access is part of the Business plans; Premium and the free plan do not include it. Every response tells you where you stand on the monthly quota, so you never have to guess:

X-RateLimit-Limit: 100000
X-RateLimit-Remaining: 99412
X-RateLimit-Period: 2026-08

Going over the burst answers 429 RATE_LIMITED, and a call refused that way does not count against the month. Running out of the monthly quota answers 429 API_QUOTA_EXCEEDED until the next month begins.

Errors

Failures return a JSON body with a stable code. Branch on the code, not the message — messages get reworded, codes do not.

CodeHTTPMeaning
INVALID_REQUEST400The request was malformed or a value was refused. When the refusal is about specific fields, the body names them in a fields array.
API_KEY_INVALID401No key, malformed key, or no such key.
API_KEY_REVOKED401The key was revoked, or its creator changed their password after creating it. Create a new key.
API_KEY_EXPIRED401The key passed its expiry date.
API_KEY_PERSONAL_UNSUPPORTED401A personal key, from before keys belonged to workspaces. Personal keys no longer work; an Admin of a Business workspace creates a workspace key instead.
API_WORKSPACE_ACCESS_REVOKED401The key's creator is no longer an active member of its workspace, or the workspace was deleted.
ACCOUNT_DISABLED401The account that created the key has been disabled.
API_REQUIRES_UPGRADE402This workspace has no Business plan right now, so its keys are paused. They work again when it has one.
WORKSPACE_UNPAID402The workspace's plan is not active, so its records are read-only: an update or delete is refused.
WORKSPACE_PLAN_REQUIRED402The workspace's plan has lapsed, so it takes no new records.
WORKSPACE_VENDOR_LIMIT402A new active record would go over the workspace's vendor limit (100 on Business Starter, a fair-use ceiling on Business). The body carries limit and used.
API_SCOPE_MISSING403The key is valid but lacks the scope this route needs.
API_WORKSPACE_ROLE_REVOKED403The key's creator is no longer an Admin of its workspace. The key works again if they are made an Admin again.
EMAIL_NOT_VERIFIED403The key's creator has not verified their email address.
FORBIDDEN403The key's creator does not have the workspace role this write needs.
RECORD_FORBIDDEN403The workspace's record rules do not let the key's creator make this change to this record.
OWNER_CHANGE_FORBIDDEN403Only an Admin can change who is accountable for a record (ownerUserId).
RATE_LIMITED429Too many requests: this key went over its per-minute burst, or 300 requests from your IP address failed authentication within 15 minutes. Wait and retry.
API_QUOTA_EXCEEDED429This key has used its monthly call quota. It resets at the start of next month (UTC).
API_UNAVAILABLE503The key could not be checked just now. Retry shortly; the response carries Retry-After.

A record id that matches nothing in the key's workspace answers 404 NOT_FOUND. If the API itself is switched off, every path answers 404 with no code at all; API_UNAVAILABLE only means a key could not be checked for a moment.

Versioning

The version is in the path, from the first release. Within v1 we only add: new endpoints, new optional parameters, new fields on a response. Removing a field, renaming one, or changing what a value means would be v2. Write clients that ignore fields they do not recognise.

Known limitations

Stated plainly, because you would find them anyway:

  • No sandbox

    Every key reads and writes the workspace's real records; there is no separate test environment. Do not point a test suite at a workspace you rely on.

  • No outbound webhooks yet

    Nothing calls you when a renewal approaches; you poll /records/upcoming instead. Signed webhooks are planned, and are what a Slack or ticketing integration should eventually be built on.

  • No bulk endpoint or idempotency key

    Import one record per request for now. The server does de-duplicate records that look identical, but that is a safety net rather than a guarantee you can build a retry loop on.

Building something with this?

We would rather hear about it early and shape the API around it than guess. Tell us what you are integrating and what is missing.