Skip to content

Authentication and API keys

Create an API key, see which routes accept it, and handle responses, errors, pagination, rate limits and idempotency.

Your code authenticates to the Sureshake API with an API key. A key acts as the person who created it, so it can see and change only what that person can.

Create an API key

You create and manage keys in the product. The /v2/api-keys routes accept only a signed-in session, so a key cannot create or rotate another key.

  1. Sign in and open Settings.
  2. Open the Security section.
  3. Under API Keys, select Create API Key.
  4. Fill in the form:
    • Key Name, which is required.
    • Description (Optional).
    • Permissions. See the note below.
    • Expiration: Never, 30 days, 90 days or 1 year.
  5. Select Create Key and copy the key. Sureshake shows it once and keeps only a hash, so you cannot see it again.

The form also has a Rate Limit (requests/hour) field. It has no effect at the moment. Every key gets the same limits.

To replace a key, create the new one, switch your code to it, then select Revoke key on the old one in the list. A revoked key stops working at once.

Send the key

Send the key in the X-API-Key header:

curl -sS https://api.sureshake.com/v2/organizations/me \
  -H "X-API-Key: $SURESHAKE_API_KEY"

Authorization: Bearer sk_... works the same way on any route that accepts keys. If you send both, X-API-Key wins.

Use keys from a server. The API’s CORS rules do not allow the X-API-Key header, and a key in browser code is visible to anyone who loads the page.

Permissions

Permission Allows
Read Routes that read data
Write Routes that read or change data

Which routes accept an API key

These routes accept a key:

Area Routes Permission
Organizations Everything under /v2/organizations: your organizations and invitations, members, creating an organization, the organization wallet Read for GET, Write otherwise
Directory search and profiles GET /v2/directory, /summary, /compare, /{subjectType}/{slug} and its /record-summary and /profile-experience, and POST /v2/directory/engagement/clicks Optional. These work without a key
Directory listings /v2/directory/listings/... (provision, draft, preview, publish, unpublish, rename, analytics, packages, claim requests, verification exceptions), /v2/directory/packages/{id}, /v2/directory/person-profile/publication, /v2/directory/entity-profile/{entityId}/publication, /v2/directory/workflows/{id}, /v2/directory/collections/{collectionId} Read for GET, Write otherwise
Directory follows and corrections /v2/directory/{subjectType}/{listingId}/follow, /follow-state, /follow-preferences, /follow-requests, /corrections Read for GET, Write otherwise
Collections Everything under /v2/entities/{entityId}/collections, plus GET /v2/collections/templates, GET /v2/collections/{id}/shared and POST /v2/collections/invites/{code} Read for GET and for POST .../agent/messages, Write otherwise
Companies POST /v2/entities, and /v2/entities/{id}/models/{templateKey}/scenarios Read for GET, Write otherwise
Reports GET /v2/reports/{id}, /document, /publication-composition, POST /v2/reports/{id}/views, POST /v2/reports/shares/{shareId}/viewed Read
Search GET /v2/search/omni Optional. Without a key you get directory results only

The other parts of the reference don’t accept keys at all: users, api-keys, filings, execution, analytics, billing, documents, hiring, vault, notifications, reporter, startups and watchlists.

Within the areas above, routes not listed here need a session too. For example, a key can read a report by ID but cannot list your reports, and it can create a company but cannot list your companies. To get an ID, open the company or report in Sureshake and copy it from the URL: /entities/<id> or /reports/<id>.

Errors when authenticating

Status error.code error.message Meaning
401 UNAUTHORIZED Invalid or expired API key The key is wrong, revoked or expired
401 UNAUTHORIZED Authentication failed. The route does not accept keys, or you sent no credential
403 FORBIDDEN API key scope 'read' is not permitted for this operation (requires 'write') Your key’s permission is too low
403 FORBIDDEN Varies The key is valid, but its owner has no access to this resource

On routes where a credential is optional, such as GET /v2/directory and GET /v2/search/omni, an invalid key does not cause an error. The request runs as anonymous and you get public results only.

Response formats

Dates are ISO 8601 strings. Most routes return one of two shapes.

{
  "data": { "id": "org-acme-holdings", "name": "Acme Holdings" },
  "meta": { "timestamp": "2026-11-03T18:00:00.000Z" }
}
{
  "data": [{ "id": "org-acme-holdings" }],
  "meta": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}

Some routes use their own shape. GET /v2/directory returns data, pagination, resultCount and facets. GET /v2/search/omni returns status, workspace and directory. The response schema on each route’s reference page is the one to follow.

Errors

Errors have this shape:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Entity not found",
    "details": {},
    "traceId": "V1StGXR8_Z5jdHi6B-myT"
  }
}

Branch on the status and code, not on message. details and traceId are optional. Errors from the authentication step have no traceId in the body.

Status error.code Notes
400 FST_ERR_VALIDATION The request failed schema validation. message is a JSON array of issues as a string, each with a path and message
400 BAD_REQUEST The request was well formed but can’t be carried out
401 UNAUTHORIZED See errors when authenticating
403 FORBIDDEN You don’t have access
404 NOT_FOUND The resource wasn’t found
409 CONFLICT For example, an organization with this name or slug already exists
410 GONE The resource is no longer available
412 PRECONDITION_FAILED A condition for the operation isn’t met
422 UNPROCESSABLE_ENTITY See idempotency
429 TOO_MANY_REQUESTS, RATE_LIMITED See rate limits
500 INTERNAL_SERVER_ERROR An unexpected error. If it keeps happening, report it with the trace ID
503 DEPENDENCY_UNAVAILABLE Something the API depends on is down. Retry with backoff

You may see other codes. Go by the status when you don’t recognize one.

Validation runs before authentication, so a malformed request can return 400 even if your key is invalid.

A path that doesn’t exist returns a different shape: { "message": "...", "error": "Not Found", "statusCode": 404 }.

Trace IDs

Every response has an x-trace-id header. You can also set your own by sending x-trace-id. Include the value when you report a problem.

Pagination

List routes page in one of two ways. The route’s reference page shows which.

Offset. Pass limit and offset. limit is usually 1 to 100 and defaults to 20. Keep adding limit to offset while meta.hasMore is true.

curl -sS "https://api.sureshake.com/v2/entities/$ENTITY_ID/collections?limit=100&offset=100" \
  -H "X-API-Key: $SURESHAKE_API_KEY"

Cursor. GET /v2/directory returns pagination.nextCursor. Pass it back as cursor while pagination.hasMore is true.

curl -sS "https://api.sureshake.com/v2/directory?limit=100&cursor=$NEXT_CURSOR" \
  -H "X-API-Key: $SURESHAKE_API_KEY"

Rate limits

Applies to Limit
All routes 2,000 requests per minute for each key. Without a credential, the limit applies per IP address
GET /v2/directory, /compare, /summary, GET /v2/search/omni 240 per minute with a valid credential, 60 per minute without
POST /v2/directory/engagement/clicks 120 per minute with a valid credential, 30 per minute without
/v2/mcp 120 per minute

Responses carry x-ratelimit-limit and x-ratelimit-remaining, plus x-ratelimit-reset. Don’t parse the reset value on its own: on the directory and search routes above it is a timestamp, and elsewhere it is a number of seconds.

When you go over a limit, you get a 429 with a retry-after header in seconds. Wait that long, then retry. The code is RATE_LIMITED on the directory and search routes above and TOO_MANY_REQUESTS elsewhere, so check the status rather than the code.

Idempotency

Some write routes let you retry safely. Send a fresh UUID in X-Idempotency-Key for each logical operation, and reuse it if you retry that operation.

With an API key, this works on these routes:

  • POST /v2/organizations
  • POST /v2/organizations/{id}/members
  • PATCH /v2/organizations/members/{memberId}
  • POST /v2/organizations/invitations/{memberId}/accept
  • POST /v2/organizations/invitations/{memberId}/decline

Other routes ignore the header.

curl -sS -X POST https://api.sureshake.com/v2/organizations \
  -H "X-API-Key: $SURESHAKE_API_KEY" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme Holdings"}'

This needs a key with Write permission, and it creates a real organization. On success you get 201 and the new organization in data.

What happens Response
Same key and body, and the first request finished with a 2xx The stored response again, with x-idempotency-replayed: true
Same key, and the first request is still running 409 CONFLICT
Same key, different method, URL or body 422 UNPROCESSABLE_ENTITY
The first request failed The key is released, so a retry runs the operation again

Keys belong to the user who sent them, and only 2xx responses are stored.

Type to search…

↑↓ navigate↵ selectEsc close