Diese technische Dokumentation ist auf Englisch verfügbar.
Leitfäden

ExpiryEdge API guide

Manage expiries, reminders, contacts and Document Collection requests from your own code. New to APIs? Start with Getting started.

Full reference: openapi.yaml (OpenAPI 3.1 - open it in Swagger UI, Postman or Insomnia).

Contents: Authentication - Base URLs and versioning - Common workflows - Conventions - Errors - Rate limits - Idempotency - Pagination and polling - Webhooks - Deprecation policy - Changelog


Authentication

Send a credential on every call (except public ones such as shared expiry links, the Document Collection fill page, sign-up and password reset):

Authorization: Bearer <api key or token>
CredentialUse forLifetime
API key ee_live_... (recommended)Scripts and integrationsUntil revoked, or its optional expires_at
Session token from POST /loginActing as a person30 days; no refresh token - call /login again on 401
Firebase ID tokenApps using the Firebase Auth SDK1 hour; call user.getIdToken() before each request

API key. An admin creates it in Settings > API keys. It belongs to the organization, uses no seat, and acts as editor (default) or viewer - never admin. You can also send it as X-API-Key: <key>. Step-by-step: API keys.

Session token.

curl -s https://api.expiryedge.com/v1/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"automation@yourcompany.com","password":"<password>"}'
# -> { "accessToken": "eyJhbGciOi...", "user": {...}, "teams": [...] }
  • Wrong email or password returns 401 {"message":"Invalid email or password"}.
  • Changing the password or "Sign out everywhere" invalidates all session tokens.
  • /login is tightly rate limited. Cache the token.

Roles. Every call is limited to your organization's data and your role. Too low a role returns 403. Each endpoint's required role is in its openapi.yaml description.

RoleReadCreate / edit / archiveDelete recordsUsers, billing, settings
ViewerYesNo (comments only)NoNo
EditorYesYesYesNo
AdminYesYesYesYes

Keep keys and tokens out of source control, URLs and logs. If a key leaks, revoke it. If a session token leaks, change the user's password.

Base URLs and versioning

Base URL
Production (use this)https://api.expiryedge.com/v1
Legacy, unversionedhttps://api.expiryedge.com
  • The old address https://us-central1-stylingsphere.cloudfunctions.net/api still works.
  • Endpoints are addressed by name: POST {base}/createExpiry, GET {base}/getExpiry?id=....
  • /v1/ behaves like the unversioned path and adds X-API-Version: 1.
  • Preview: resource-style v2 routes - see Try the v2 API (preview).

Common workflows

All examples assume BASE=https://api.expiryedge.com/v1 and TOKEN=<your API key>.

Create an expiry with reminders

# 1. Create the expiry. `contacts` are contact IDs (getAllContacts).
curl -s "$BASE/createExpiry" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: import-lic-4471' \
  -d '{"name":"Dental license - Northwind","type":"License","expiry_date":"2027-03-31","contacts":["cK9x2..."]}'
# -> includes the new "id"

# 2. Add reminders: 60 and 14 days before, 09:00 org time
curl -s "$BASE/createExpiryReminders" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"expiry_id":"<id>","expiry_date":"2027-03-31","org_timezone":"Europe/Amsterdam",
       "reminders":[{"amount":60,"time_unit":"day","period":"before","time":"09:00","email":true},
                    {"amount":14,"time_unit":"day","period":"before","time":"09:00","email":true}]}'
  • Referenced contacts, assignees and folders must be in your organization, or you get 400 with the IDs in details.
  • Recurring: set is_recurring: true, recurrence_type (daily|weekly|monthly|yearly) and recurrence_interval. Marking it done creates the next one.
  • Send a reminder now: sendExpiryNotificationNow.

Add a contact

curl -s "$BASE/createContact" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: crm-contact-88213' \
  -d '{"firstName":"Priya","lastName":"Shah","email":"priya@northwind.example","phone":"+31612345678"}'

Many at once: bulkImportContacts. Over your plan's contact limit returns 403 with current, limit and remaining.

Send a Document Collection request

# templateId from getAllCollectionTemplates; use groupId instead of contactIds for a contact group
curl -s "$BASE/createCollectionRequests" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: onboarding-northwind-2026-10' \
  -d '{"templateId":"tpl_onboarding","contactIds":["cK9x2..."],"name":"Insurance certificates","dueDate":"2026-11-15"}'
# -> { "created": 1, "emails_sent": 1, "request_ids": ["req_7Hq..."], ... }

Optional expiryId links the collected documents to an expiry. Get results with the submissions feed or a webhook.

Files

Conventions

RequestsJSON bodies (Content-Type: application/json). "Get one" endpoints usually take ?id=.
DatesISO 8601. Calendar dates (expiry_date: "2027-03-31") and reminder time use your organization's timezone. Timestamps (created_at, ...) are UTC.
IDsOpaque strings. Don't parse them.
Server-owned fieldsid, organization_id, user_id, created_at, share_token etc. are ignored if sent.
SuccessTreat any 2xx as success (most creates return 201, a few 200).
Time limitA request can run for at most 60 seconds. Send bulk imports in batches (e.g. 1,000 rows).

Errors

{ "error": "Some of the information provided was invalid.", "code": "VALIDATION_FAILED", "requestId": "a3f09c1be2d4", "details": {} }

error is safe to show users. code is stable. Quote requestId to support. Some older endpoints return only error (or message for /login), so branch on the HTTP status first.

StatuscodeWhat to do
400VALIDATION_FAILED, BAD_REQUESTFix the request.
401UNAUTHORIZED, TOKEN_INVALID, SESSION_EXPIREDGet a new token, retry once.
403FORBIDDEN or plan limit (current/limit/remaining)Role too low or plan limit reached. Don't retry.
404NOT_FOUNDMissing, or in another organization.
405METHOD_NOT_ALLOWEDUse the right HTTP method.
409CONFLICT, IDEMPOTENCY_IN_PROGRESSConflict, or same Idempotency-Key still running - retry after Retry-After.
422IDEMPOTENCY_KEY_REUSEDKey reused with a different body. Use a new key.
429-Wait Retry-After seconds.
5xxINTERNAL_ERROR, TIMEOUTRetry with backoff (and an Idempotency-Key on creates).

Rate limits

Per endpoint and client IP, fixed windows:

TierLimitEndpoints
Strict10 / 15 minlogin, register, sendPasswordResetEmail, verifyCodeAndUpdatePassword, resendVerificationEmail, checkPromoCode, sendSampleReminderEmail, submitSignupRequest
Sensitive30 / 15 mincompleteInvitation, verifyEmail, invite, resendInvitation, sendExpiryNotificationNow
Extraction (AI)30 / minextractDocument, extractDocumentDirect, bulkExtractDocuments, mapImportColumns
High500 / mingetPaginatedExpiries, getExpiryMetrics, getAllExpiries
Default60 / minEverything else

Responses carry X-RateLimit-Remaining. A 429 has Retry-After (seconds) and { "error": "Rate limit exceeded", "retryAfter": 42 }. Plan quotas (expiries, contacts, storage, SMS, AI scans) are separate and return 403.

Idempotency

Send Idempotency-Key: <1-255 chars of A-Z a-z 0-9 - _ : .> on creates so retries don't make duplicates.

  • Supported on: createExpiry, createContact, createContactGroup, createFolder, createComplianceClient, createComplianceProject, createCompliance, createCollectionRequests, createCollectionTemplate, createWorkflowRun, createExpiryComment, createKnowledgeDoc, the file upload-URL and complete-upload endpoints, sendExpiryNotificationNow, bulkImportExpiries, bulkImportContacts, invite, createApiKey (a replay returns "key": null).
  • A 2xx response is stored for 24 hours. Same key + same body returns it again with Idempotent-Replayed: true.
  • Same key, different body: 422 IDEMPOTENCY_KEY_REUSED. Still running: 409 IDEMPOTENCY_IN_PROGRESS.
  • Failed calls aren't stored, so you can fix and retry with the same key.
  • Keys are scoped to your user and endpoint. Derive them from your own record IDs.

Pagination and polling

EndpointPagingUse for
getPaginatedExpiriespage / limit, filters, sortingBrowsing
getAll* (e.g. getAllContacts)Returns everything (some take limit)Small datasets - not polling
listExpiryChanges, listCollectionSubmissionsCursor, oldest first, limit up to 100 (default 50)Sync and polling

Cursor feeds return { data, next_cursor, has_more }. Start with since=<ISO time>, then pass cursor=<next_cursor>. When has_more is false, save next_cursor and poll later.

curl -s "$BASE/listCollectionSubmissions?since=2026-10-01T00:00:00Z&limit=50" -H "Authorization: Bearer $TOKEN"
curl -s "$BASE/listExpiryChanges?event=completed&since=2026-10-01T00:00:00Z" -H "Authorization: Bearer $TOKEN"

listExpiryChanges takes event=created|updated|completed. Download a submission file with getCollectionSubmissionFile; review it with reviewCollectionSubmission.

Webhooks

  • Outgoing webhooks (admins): ExpiryEdge POSTs signed events to your https URL. Events: expiry.created, expiry.updated, expiry.completed, expiry.deleted, collection_request.completed, collection_submission.received. Guide: Get instant updates with webhooks.
  • Teams / Slack reminders: set an incoming-webhook URL in Settings > Integrations (saveWebhooks).
  • Can't receive requests? Use the cursor feeds.

Versioning and deprecation policy

  • Current version: v1. Within v1, changes are additive only (new endpoints, optional fields, response fields, error codes). Ignore unknown fields.
  • Breaking changes ship only in a new version. The old version stays for at least 12 months after the new one is released.
  • Deprecations are marked deprecated: true in openapi.yaml and announced here at least 6 months before removal, with a Deprecation header where possible.
  • Security fixes can ship immediately.

Changelog

2026-09-28

  • Docs published at expiryedge.com/developers/api, with an interactive reference.

2026-09-27 - v1

Added

  • Organization API keys (createApiKey, getApiKeys, revokeApiKey), via Authorization: Bearer ee_live_... or X-API-Key.
  • /v1/ base path (X-API-Version: 1).
  • Idempotency-Key on create and send endpoints.
  • Cursor feeds: listExpiryChanges, listCollectionSubmissions.
  • Expiry comments: getExpiryComments, createExpiryComment, updateExpiryComment, deleteExpiryComment.
  • Expiry attachments: getExpiryAttachments, getExpiryAttachmentUploadUrl, completeExpiryAttachmentUpload, deleteExpiryAttachment.
  • Documents: getFolderFiles, getFolderFileUploadUrl, completeFolderFileUpload, renameFolderFile, moveFolderFile, deleteFolderFile.
  • Docs pages: getKnowledgeDocs, getKnowledgeDoc, createKnowledgeDoc, updateKnowledgeDoc, deleteKnowledgeDoc, getKnowledgeDocAttachments, getKnowledgeDocAttachmentUploadUrl, completeKnowledgeDocAttachmentUpload, deleteKnowledgeDocAttachment.
  • Outgoing webhooks: createWebhookSubscription, getWebhookSubscriptions, updateWebhookSubscription, deleteWebhookSubscription, rotateWebhookSecret, sendTestWebhook, getWebhookDeliveries.
  • Document Collection upload-URL responses include uploadHeaders (send them all with the PUT).
  • getReferralCodeDetails without a token returns public offer terms.
  • updatePassword accepts currentPassword.
  • markExpiryDone accepts the id as id or expiryId in the body.
  • OpenAPI 3.1 reference and these guides.

Changed

  • Auth failures always return 401 (some returned 400/500).
  • References to another organization's contacts, users, folders or directory entries return 400.
  • Deleting expiries and creating share links need Editor or Admin.
  • updateExpiry: empty share_password keeps the current one; null removes it.
  • New rate-limit tiers (sensitive, extraction); password reset and resend-verification moved to strict.
  • Timestamps are always ISO 8601 strings (some endpoints used to return {_seconds, _nanoseconds}).

Security

  • Access-control fixes: organization scoping, role checks on writes, webhook signature verification, internal endpoints closed.