Enterprise plan

API reference

Manage your digital business cards programmatically, pull scan analytics into your own systems, and receive real-time webhook notifications for card, scan, and lead events.

This API manages personal cards. ShepCard Business customer cards are managed in the Business workspace and are excluded from personal API tokens. See the Business guide for customer workflows.

https://shepcard.com/api/v11,000 requests / hour per tokenGet started

Quickstart

  1. 1. Upgrade to Enterprise

    The API is an Enterprise feature. Upgrade from Dashboard → Billing.

  2. 2. Create an API token

    Go to Settings → Developer and create a token with the scopes you need. Copy it immediately - it is shown only once.

  3. 3. Make your first request

    Call any endpoint with the token in the Authorization header. Try it in the playground below.

bash
curl https://shepcard.com/api/v1/cards \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"

Authentication

All endpoints require a Bearer token in the Authorization header. Tokens start with scp_ followed by 48 hex characters and can carry any combination of three scopes:

ScopeGrants
readList, get, and view cards
writeCreate, update, and delete cards; register webhooks
analyticsFetch scan analytics

Tokens are stored hashed

ShepCard stores only a SHA-256 hash of your token. The plaintext is shown exactly once at creation and can never be recovered - delete and recreate if you lose it.

Revocation is instant

Deleting a token in Settings stops it working immediately. Plan downgrades are also enforced per request, so a lapsed Enterprise subscription disables tokens right away.

Endpoints

GET/cardsread

Returns all of your cards, newest first. Pass ?archived=true to include archived cards (excluded by default).

bash
curl "https://shepcard.com/api/v1/cards" \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
json - 200 OK
{
  "cards": [
    {
      "id": "cmsf2y1k20001abcde1234567",
      "name": "Demo Card",
      "slug": "demo",
      "published": true,
      "archived": false,
      "theme": { "palette": "ocean", "colors": { "primary": "#0ea5e9" } },
      "background": null,
      "sections": [],
      "seoTitle": null,
      "seoDescription": null,
      "viewCount": 128,
      "teamId": null,
      "createdAt": "2026-07-20T09:14:22.000Z",
      "updatedAt": "2026-08-02T16:40:05.000Z"
    }
  ]
}
POST/cardswrite

Creates a card and fires the card.created webhook. Your plan's card limit applies here too - exceeding it returns 403.

FieldTypeRequired
namestringyes
slugstring (custom URL; defaults to a slug from name)no
publishedbooleanno
themeobject (palette + colors)no
backgroundstring | null (image URL)no
sectionsarray (ordered card sections)no
bash
curl -X POST https://shepcard.com/api/v1/cards \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Lovelace - Card",
    "published": true,
    "theme": { "palette": "ocean" },
    "sections": [
      { "id": "sec_1", "type": "profile", "data": { "name": "Ada Lovelace", "title": "Mathematician" } },
      { "id": "sec_2", "type": "contact", "data": { "email": "[email protected]", "phone": "+233 20 000 0000" } }
    ]
  }'
json - 201 Created
{
  "card": {
    "id": "cmsf3aaaa0001abcd12345678",
    "name": "Ada Lovelace - Card",
    "slug": "ada-lovelace-card",
    "published": true,
    "archived": false,
    "theme": { "palette": "ocean" },
    "background": null,
    "sections": [
      { "id": "sec_1", "type": "profile", "data": { "name": "Ada Lovelace", "title": "Mathematician" } },
      { "id": "sec_2", "type": "contact", "data": { "email": "[email protected]", "phone": "+233 20 000 0000" } }
    ],
    "seoTitle": null,
    "seoDescription": null,
    "viewCount": 0,
    "teamId": null,
    "createdAt": "2026-08-04T10:00:00.000Z",
    "updatedAt": "2026-08-04T10:00:00.000Z"
  }
}
GET/cards/{cardId}read

Returns one card. A card that does not exist - or belongs to another account - returns 404, never 403.

bash
curl https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678 \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
PUT/cards/{cardId}write

Partial update - send only the fields you want to change. All create fields are accepted, plus seoTitle and seoDescription. Changing slug to one already in use returns 409. Fires card.updated.

bash
curl -X PUT https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678 \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Lovelace - Consulting",
    "seoDescription": "Mathematician and writer, available for consulting."
  }'
DELETE/cards/{cardId}write

Permanently deletes a card (the dashboard shares the same delete - nothing is restorable). Fires card.deleted with {id, name, slug}.

bash
curl -X DELETE https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678 \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
json - 200 OK
{ "success": true }
GET/cards/{cardId}/scans?days=30analytics

Scan analytics for one card. days (1–365, default 30) controls the range of the daily series and breakdowns; totalScans and recentScans cover all history.

bash
curl "https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678/scans?days=30" \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
json - 200 OK
{
  "card": { "id": "cmsf3aaaa0001abcd12345678", "name": "Ada Lovelace - Card", "slug": "ada-lovelace-card" },
  "totalScans": 42,
  "scansThisMonth": 9,
  "uniqueVisitors": 3,
  "byDay": [
    { "date": "2026-07-06", "scans": 0 },
    { "date": "2026-07-07", "scans": 2 },
    { "date": "2026-07-08", "scans": 1 }
  ],
  "byDevice": [
    { "device": "Mobile", "count": 34 },
    { "device": "Desktop", "count": 8 }
  ],
  "byCountry": [
    { "country": "GH", "count": 30 },
    { "country": "US", "count": 12 }
  ],
  "recentScans": [
    {
      "id": "cmsf4bbbb0002efgh23456789",
      "country": "GH",
      "device": "Mobile",
      "browser": "Chrome",
      "referrer": null,
      "createdAt": "2026-08-04T08:12:01.000Z"
    }
  ]
}
POST/webhookswrite

Registers a webhook endpoint. The URL must be https:// - plain HTTP is rejected with 400. The signing secret is included only in this response.

FieldTypeRequired
urlstring (https:// only)yes
eventsarray: card.created, card.updated, card.deleted, scan.recorded, lead.createdyes
bash
curl -X POST https://shepcard.com/api/v1/webhooks \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/shepcard",
    "events": ["card.created", "card.updated", "scan.recorded"]
  }'
json - 201 Created
{
  "webhook": {
    "id": "cmsf5cccc0003ijkl34567890",
    "url": "https://example.com/hooks/shepcard",
    "events": ["card.created", "card.updated", "scan.recorded"],
    "secret": "920a80fc3b31980514133a3cdbfe9a403706dc3ef3cd04f70607d10eef762738",
    "active": true,
    "createdAt": "2026-08-04T10:05:00.000Z",
    "updatedAt": "2026-08-04T10:05:00.000Z"
  }
}

Try it live

API playground

Pick an endpoint, paste your token, and send a live request from your browser. Your token is used in memory only - it is never stored or logged.

GET/api/v1/cardsscope: read

List your cards. Add ?archived=true to include archived cards.

Tip: create a token in Settings → Developer first. Requests go to /api/v1 on this origin - use the Postman collection or curl for cross-origin workflows.

Webhooks

Webhooks push events to your endpoint the moment they happen. Each delivery is an HTTP POST with a JSON body and three custom headers:

delivery headers
Content-Type: application/json
x-shepcard-event: scan.recorded
x-shepcard-delivery: 3f9c2c1e-aaaa-bbbb-cccc-3f9c2c1e0000
x-shepcard-signature: <hex HMAC-SHA256 of the raw body>
json - request body
{
  "event": "scan.recorded",
  "data": {
    "id": "cmsf4bbbb0002efgh23456789",
    "cardId": "cmsf3aaaa0001abcd12345678",
    "cardSlug": "ada-lovelace-card",
    "createdAt": "2026-08-04T08:12:01.000Z",
    "ip": "154.160.6.10",
    "country": "GH",
    "device": "Mobile",
    "browser": "Chrome",
    "referrer": null
  },
  "deliveryId": "3f9c2c1e-aaaa-bbbb-cccc-3f9c2c1e0000",
  "deliveredAt": "2026-08-04T08:12:01.437Z"
}
EventFired whenPayload data
card.createdA card is created (API or dashboard)Full card object
card.updatedA card is updated (API or dashboard)Full card object
card.deletedA card is deleted{id, name, slug}
scan.recordedSomeone scans your cardScan details (id, cardId, device, country, …)
lead.createdA lead form is submitted on your cardLead details (id, cardId, name, email, message)

Verify every signature

Compute HMAC-SHA256 of the raw request body with your webhook's secret and compare it to x-shepcard-signature. Never trust an unverified delivery.

node.js
const crypto = require('crypto')

// rawBody: the exact body string - do not re-stringify the parsed JSON
function verifySignature(rawBody, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  if (expected.length !== signature.length) return false
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(signature, 'hex')
  return crypto.timingSafeEqual(a, b)
}

Retry policy

  • • 2xx/3xx - delivery complete
  • • 4xx - no retry (fix the payload)
  • • Network error, timeout, or 5xx - up to 3 attempts at ~1s and ~5s
  • • Each attempt has a 10-second timeout

Best practices

  • • Respond 2xx fast; do heavy work asynchronously
  • • Deduplicate with deliveryId - retries can repeat an event
  • • Return 4xx for malformed payloads, never 5xx
  • • Test locally with webhook.site or ngrok (https required)

Rate limits

Each token is limited to 1,000 requests per hour (rolling window). Exceeding the limit returns 429 with a Retry-After header telling you how many seconds to wait.

bash
curl -i https://shepcard.com/api/v1/cards \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"

HTTP/1.1 429 Too Many Requests
retry-after: 3590
{ "error": "Rate limit exceeded" }

Each token has its own window - create additional tokens per integration if you need more sustained volume.

Errors

Errors always return JSON with an error message.

json - 409 Conflict
{ "error": "This URL is already taken" }
StatusMeaning
400Invalid request - missing or malformed field
401Missing or invalid Authorization header / token
403Plan does not include the API, token lacks the scope, or card limit reached
404Card or webhook not found (non-owned cards return 404 too)
409Slug collision - a card with that URL already exists
429Rate limit exceeded - see Retry-After
500Internal server error - retry; contact support if it persists

Postman

The fastest way to explore the API interactively outside the browser - a ready-made collection covers every endpoint.

1.

Download the collection - shepcard-api.postman_collection.json

2.

Import it in Postman - File → Import (or drag the file in). You get a ShepCard API collection with requests organized under Cards / Analytics / Webhooks.

3.

Set one variable - baseUrl already points at https://shepcard.com/api/v1 and every request carries Authorization: Bearer {{token}}, so the only thing to fill in is token = your scp_… token. Point baseUrl at your dev server instead if you are testing locally.

A typical flow

  1. Cards → Create a card - the response's cardId is saved to a variable automatically.
  2. Cards → List cards - your new card appears.
  3. Cards → Update a card - change the name and watch it reflect in List cards.
  4. Webhooks → Register a webhook - paste a https://webhook.site URL, then create a card and watch the delivery arrive with its signature headers.
  5. Cards → Delete a card - cleanup when done.