Errors

Every error response from the Curviate API uses the same flat JSON envelope. Machine-readable codes let your agent handle failures programmatically.

Error envelope

{
  "code": "UNAUTHORIZED",
  "message": "Valid API key required. Use Authorization: Bearer cvt_live_<key>.",
  "retry_hint": null,
  "user_fixable": true,
  "retry_likely_to_succeed": false
}

The error body is a flat JSON object — there is no wrapper "error" key. The top-level properties are code, message, retry_hint, user_fixable, retry_likely_to_succeed, and optionally required_tier and retry_after_ms.

FieldTypeAlways presentDescription
codestringYesMachine-readable error code from the taxonomy below
messagestringYesHuman-readable description
retry_hintobject or nullYesnull when no retry guidance; {"kind":"delay","delayMs":N} / {"kind":"backoff"} / {"kind":"never"} when present
user_fixablebooleanYestrue when the caller can resolve the error (e.g. fix a bad parameter, add a seat)
retry_likely_to_succeedbooleanYestrue when retrying the same request may succeed (e.g. rate-limit window will expire)
required_tierstring or absentNoPresent only on TIER_NOT_ACTIVE; values: "core", "sn", "sales_nav", "recruiter"
retry_after_msnumber or absentNoPresent on rate-limit errors when a Retry-After header was returned; milliseconds to wait before retrying

HTTP status codes

StatusMeaning
400Bad request — invalid parameters or body
401Unauthorized — missing or invalid API key
403Forbidden — valid key, insufficient tier or no subscription
404Not found — resource does not exist
413Payload too large — request body exceeds the size limit
415Unsupported media type — wrong Content-Type header
429Rate limited — quota exceeded (see Rate limits)
500Internal error — unexpected server-side failure, safe to retry with backoff
503Service unavailable — temporary service issue, safe to retry

Error codes

Every error response carries one of these stable codes. Agent code can use an exhaustive switch statement over error.code without fear of unknown values.

Authentication and request validation

CodeHTTPDescription
UNAUTHORIZED401API key missing, malformed, or revoked
INVALID_REQUEST400Request body or parameters failed schema validation
UNSUPPORTED_MEDIA_TYPE415Content-Type header is missing or not application/json / multipart/form-data
PAYLOAD_TOO_LARGE413Request body exceeds the maximum allowed size

Account state

CodeHTTPDescription
ACCOUNT_NOT_FOUND404The account_id does not exist or does not belong to this workspace
ACCOUNT_RESTRICTED403The account exists but is restricted from performing this operation
RESOURCE_NOT_FOUND404A non-account resource (e.g. chat, message, invitation, webhook) was not found

Subscription and tier gating

CodeHTTPDescription
TIER_NOT_ACTIVE403The endpoint requires a tier add-on that is not active on this account's seat. The required_tier field names the needed tier (sn, sales_nav, or recruiter).
LINKEDIN_FEATURE_NOT_SUBSCRIBED403The LinkedIn account does not have the premium feature the endpoint requires (e.g. InMail, Sales Navigator, Recruiter).

Rate limits

CodeHTTPDescription
RATE_LIMIT_ACCOUNT429The per-account (per LinkedIn account) rate limit was exceeded. Wait for the retry_after_ms or the Retry-After response header before retrying.
RATE_LIMIT_TENANT429The per-workspace (tenant) rate limit was exceeded. retry_after_ms is present.
PLATFORM_RATE_LIMIT429A rate limit was reached. Back off for retry_after_ms or a minimum of 60 seconds.

Platform errors

CodeHTTPDescription
PLATFORM_ERROR500An unexpected error occurred. Retrying is likely to succeed after a short backoff.
PLATFORM_NOT_IMPLEMENTED503The requested operation is not yet implemented for this account type or platform tier.

Account connection (checkpoint)

These codes appear during the account-connect flow (POST /v1/auth/intent, POST /v1/auth/checkpoint/solve).

CodeHTTPDescription
CHECKPOINT_NOT_FOUND404No active checkpoint exists for this account.
CHECKPOINT_EXPIRED410The checkpoint has expired. Restart the connection flow.
CHECKPOINT_INVALID_CODE400The submitted verification code was incorrect.
CHECKPOINT_MAX_ATTEMPTS429Too many incorrect code attempts. Restart the connection flow.
CHECKPOINT_ALREADY_RESOLVED409The checkpoint has already been resolved.
CHECKPOINT_UNSUPPORTED400This account type does not support the requested checkpoint flow.
CONNECTION_IN_PROGRESS409A connection attempt is already in progress for this account. Wait for it to complete.
ACCOUNT_ALREADY_LINKED409This LinkedIn account is already linked. Reconnect or disconnect the existing account instead of linking it again — not retryable as-is. When your workspace already owns it, the error names your own account_id; a checkpoint that resolves by re-adopting an identity your workspace already holds returns the account with recovered: true instead of this error.

LinkedIn account errors

These codes appear during the LinkedIn account connection and session lifecycle.

CodeHTTPDescription
LINKEDIN_AUTH_FAILED401LinkedIn rejected the credentials. Verify email and password, then retry.
LINKEDIN_RATE_LIMITED429LinkedIn is rate-limiting this account. Wait before retrying.
LINKEDIN_COOKIE_INVALID401The li_at cookie is expired or invalid. Re-export from your browser.
LINKEDIN_SERVICE_UNAVAILABLE503LinkedIn's service is temporarily unavailable. Retry after a backoff.

Messaging

CodeHTTPDescription
MESSAGE_WINDOW_EXPIRED400The message edit/delete window has closed. LinkedIn restricts edits to within a short time after sending.
RECIPIENT_UNREACHABLE400The target recipient cannot receive messages (e.g. no shared connection, privacy settings).

Billing

These codes appear on subscription and seat management endpoints.

CodeHTTPDescription
PAYMENT_REQUIRED402No active subscription. Add a seat via the dashboard.
PAYMENT_FAILED402A payment attempt failed. Update your payment method in the dashboard.
SUBSCRIPTION_BUSY503The subscription is being modified concurrently. Retry after a short delay — retry_likely_to_succeed is true.
SUBSCRIPTION_NOT_FOUND404No subscription record exists for this workspace.
SEAT_NOT_FOUND404The referenced seat does not exist or does not belong to this workspace.
SEAT_CANCELLED403The referenced seat has been cancelled.

Generic

CodeHTTPDescription
INTERNAL500An unexpected internal server error occurred. It is safe to retry with exponential backoff.

SDK: CurviateError

When using the TypeScript SDK, every API-layer failure throws a CurviateError instance. It is the typed equivalent of the REST error envelope — the same code, message, retry_hint, userFixable, retryLikelyToSucceed, requiredTier, and retryAfterMs fields are accessible as class properties.

import { Curviate, isCurviateError } from "@curviate/sdk";
 
const curviate = new Curviate({ apiKey: process.env.CURVIATE_API_KEY! });
 
try {
  await curviate.accounts.list();
} catch (err) {
  if (isCurviateError(err)) {
    switch (err.code) {
      case "RATE_LIMIT_TENANT":
        await new Promise((r) => setTimeout(r, err.retryAfterMs ?? 60_000));
        break;
      case "UNAUTHORIZED":
        console.error("Check your API key.");
        break;
      default:
        throw err;
    }
  }
}

Handling errors

# A 429 response — rate limit exceeded
HTTP/2 429
Retry-After: 37
Content-Type: application/json
 
{
  "code": "RATE_LIMIT_TENANT",
  "message": "Tenant rate limit exceeded.",
  "retry_hint": { "kind": "delay", "delayMs": 37000 },
  "user_fixable": false,
  "retry_likely_to_succeed": true,
  "retry_after_ms": 37000
}

The retry_after_ms field (when present) and the Retry-After header both carry the same wait in different units. Use either; Retry-After is the canonical IETF header value in seconds.

See Rate limits for the full client backoff algorithm.

COMPANY · LEGAL

Privacy Policy

Redmer Holding GmbHLast updated July 25, 2026

Who we are

Curviate is operated by Redmer Holding GmbH ("Curviate", "we", "us"), a German GmbH registered at Amtsgericht Bonn, HRB 29957, registered address Hostertstraße 16, 53332 Bornheim, Germany. Full company details are on our Imprint. We haven't appointed a statutory Data Protection Officer, since our processing doesn't reach the scale or sensitivity that requires one. Privacy questions go to privacy@curviate.com.

The two roles we play

When you create an account and use Curviate, we process your own data (identity, billing, API keys, connector authorizations). For that data, we are the controller.

When you use Curviate to act on your own connected LinkedIn account, viewing profiles, sending messages, managing engagement, that content and those contacts belong to that account and its people. You are the controller of that data; we are the processor, acting only on your instructions, under the Data Processing Agreement between us. If one of your contacts has a question about being reached through Curviate, you're who they should contact first; email privacy@curviate.com if you need help routing it.

What we collect, and why

DataWhy
Account identity (name, email, sign-in method)Create and secure your account
Your LinkedIn credentialsOperate the actions you request
LinkedIn content returned by an API callFulfil that specific request, nothing more
API keys and connector (OAuth) authorizationsAuthenticate your API, CLI, MCP, or SDK requests
Billing detailsCharge you correctly and meet our tax obligations
Usage and security logsKeep the service reliable and abuse-free
Support messagesRespond to you
Website analytics, only if you opt inUnderstand how the site is used

We rely on our contract with you, our legitimate interest in running and securing the service, our legal obligations (tax law, for example), and, for analytics, your consent. We never sell your data or use it to train models.

Where it's processed, and who else touches it

Our infrastructure runs in the EU. Hosting: Railway. Database and auth: Supabase, Ireland. Email: Resend. Payments: Stripe. Network security: a DDoS-protection provider sits in front of our app and never sees or stores request content. LinkedIn connectivity: a third-party infrastructure provider that lets us execute LinkedIn actions on your behalf. Error tracking: Sentry, Frankfurt. Product analytics: PostHog, Frankfurt. Uptime monitoring: Better Stack.

We give the current, named list of every provider above, plus our Data Processing Agreement, to any customer who asks: security@curviate.com.

Outside the EU

All customer LinkedIn data, account data, and telemetry are processed and stored exclusively in EU regions of our sub-processors. A few providers we rely on (Stripe and Sentry, for example) are headquartered outside the EU/EEA; where that applies, it's covered by their own GDPR safeguards, typically the EU Standard Contractual Clauses.

How long we keep it

DataRetention
Account and workspace dataWhile your account is active
Closed accountRecoverable for 7 days, then deleted on day 8
LinkedIn credentialsUntil you disconnect that account
LinkedIn contentNot stored; any transient cache clears within 1 hour, never indexed, never used for training
API keysUntil you revoke or rotate them
Connector (OAuth) authorizationsAccess token ~1 hour; refresh token up to ~12 months, or until you revoke it, whichever comes first
Billing recordsAs required by German tax law, currently up to 10 years
LogsA short operational window; metadata only, never message content

The 12-month figure above is a server-side credential for a connected AI agent or app. It is not a cookie and doesn't touch your browser session; see Cookies below for that. You can see and revoke every connector from Authorized applications in your dashboard at any time.

Cookies

We keep cookies to a minimum, and ask before anything beyond the essentials runs.

Strictly necessary, no consent needed:

NamePurposeExpiry
cc_cookieRemembers your cookie choice12 months
curviate-themeRemembers light/dark mode (local storage, not a cookie)Persistent
sb-*-auth-tokenKeeps you signed inWhile active; cleared on sign-out

Analytics, only if you accept:

NamePurposeExpiry
_gaGoogle Analytics: distinguishes visitors2 years
_gidGoogle Analytics: distinguishes visitors24 hours
_ga_<container id>Google Analytics: persists session state2 years

No advertising cookies, ever. Accept and reject are equally easy, and you can change your mind any time via Cookie Preferences in the footer; we won't ask again for 12 months unless something material changes. Our LinkedIn connect flow and OAuth authorization screen never set anything beyond the essentials, so no banner appears there.

Connecting an AI agent or app

Curviate is built for AI agents and automated clients as much as for people. If you connect an app like Claude, or your own code, via an API key or an OAuth connector, it can act on your workspace within the access you gave it. What it does with anything it receives back, including what it sends to its own AI model, is between you and that provider; review its practices before connecting it. Review and revoke any connection any time from your dashboard.

Your rights

You can access, correct, delete, restrict, or object to your data, port it elsewhere, and withdraw consent at any time: email privacy@curviate.com. Closing your account starts the 7-day recoverable window above. We don't make automated decisions about you that have a legal or similarly significant effect. You can also complain to a supervisory authority; ours is the Landesbeauftragte für Datenschutz und Informationsfreiheit Nordrhein-Westfalen (LDI NRW), www.ldi.nrw.de, though you're free to complain to the one in your own country instead.

Keeping it secure

Credentials are encrypted and never logged, returned, or shared. LinkedIn actions run through native, humanized flows; full detail is on our Security & Compliance page. If a breach puts your rights at risk, we'll notify the authorities and you, as GDPR requires. Curviate isn't directed at, or offered to, anyone under 16.

Changes

We'll update this page when our practices change, and reset the cookie prompt if the change is material.

Contact