Testing & Deployment

This page covers how to test your Hypercerts integration locally, the validation rules and constraints your records must satisfy, privacy considerations, and a checklist for going live.


Local development

Set up a test PDS

Run a local PDS to avoid polluting production data. Self-host a test instance using the ATProto PDS distribution and follow the ATProto self-hosting guide.

Point your SDK to the local instance instead of production:

TypeScript
import { createATProtoSDK } from "@hypercerts-org/sdk-core";


const sdk = createATProtoSDK({
  service: "http://localhost:2583", // Local PDS
  oauth: {
    clientId: "https://your-app.com/client-metadata.json",
    redirectUri: "https://your-app.com/callback",
    scope: "atproto",
  },
});

Use test identities

Create dedicated test accounts — never use production identities for testing. When running a local PDS, you can create accounts with any handle. Each test identity gets its own DID and repository, isolating test data from production.

Create and verify a test record

Create a record with the SDK, then read it back to confirm it was stored correctly:

TypeScript
const session = await sdk.callback(callbackParams);
const repo = sdk.getRepository(session);


// Create a test hypercert
const result = await repo.hypercerts.create({
  title: "Test: reforestation project Q1 2026",
  description: "Integration test — safe to delete.",
  workScope: "test",
  workTimeframeFrom: "2026-01-01",
  workTimeframeTo: "2026-03-31",
  rights: {
    name: "Public Display",
    type: "display",
    description: "Right to publicly display this contribution",
  },
});


console.log("Created:", result.uri);
console.log("CID:", result.cid);

The returned cid is a content hash. If the record changes, the CID changes — this is how you verify data integrity.

Clean up test data

Delete test records when you're done to keep your repository clean:

TypeScript
await repo.hypercerts.delete(result.uri);

Deletion removes the record from your PDS. Cached copies may persist in indexers temporarily.


Record constraints

When creating or updating records, the PDS validates them against the lexicon schema. Records that violate constraints are rejected.

Required fields

Every record type has required fields. The PDS returns a validation error if any are missing.

TypeScript
// ❌ Rejected — missing title and workTimeframeFrom
await repo.hypercerts.create({
  description: "Built a community garden",
});


// ✅ Accepted
await repo.hypercerts.create({
  title: "Community Garden Project",
  description: "Built a community garden",
  workScope: "Urban agriculture",
  workTimeframeFrom: "2026-01-01",
  workTimeframeTo: "2026-06-30",
  rights: {
    name: "Public Display",
    type: "display",
    description: "Right to publicly display this contribution",
  },
});

Datetime format

All datetime fields must use ISO 8601 format.

TypeScript
// ❌ Rejected
workTimeframeFrom: "01/15/2026"


// ✅ Accepted
workTimeframeFrom: "2026-01-15T00:00:00Z"

Strong references

When one record references another (e.g., an evaluation referencing an activity claim), the reference must include both the AT-URI and the CID. This makes the reference tamper-evident.

TypeScript
// ❌ Rejected — missing cid
const evaluation = {
  activity: {
    uri: "at://did:plc:abc123/org.hypercerts.claim.activity/3k7",
  },
};


// ✅ Accepted
const evaluation = {
  activity: {
    uri: "at://did:plc:abc123/org.hypercerts.claim.activity/3k7",
    cid: "bafyreiabc123...",
  },
};

If the referenced record is updated after you create the reference, the CID will no longer match. Fetch the latest version to get the current CID:

TypeScript
const latest = await repo.hypercerts.get(
  "at://did:plc:abc123/org.hypercerts.claim.activity/3k7"
);


const evaluation = {
  activity: {
    uri: latest.uri,
    cid: latest.cid,
  },
};

String and array limits

Lexicon schemas define maximum lengths for strings (in bytes and Unicode grapheme clusters) and arrays. Check the lexicon reference for specific limits on each field.

Blob uploads

Blobs (images, documents, evidence files) are uploaded to the PDS separately from records. Size limits depend on the PDS implementation — check your PDS documentation for exact values.

If your evidence files are too large for blob upload, store them externally (e.g., on IPFS or a public URL) and reference them by URI in the evidence record.

Validation error summary

ErrorCauseFix
Missing required fieldRecord omits a field the lexicon marks as requiredInclude all required fields — see the lexicon reference
Invalid datetimeDatetime not in ISO 8601 formatUse format: 2026-01-15T00:00:00Z
Invalid strong referenceReference missing uri or cidInclude both fields — fetch the latest CID if needed
String too longString exceeds maxLength or maxGraphemesTruncate or validate before submission
Array too longArray exceeds maxLengthReduce array size
Blob too largeFile exceeds PDS size limitCompress, split, or use external storage with a URI

Rate limits

PDS implementations impose rate limits on API requests. Specific limits vary by PDS — check your provider's documentation.

If you hit a rate limit, retry with exponential backoff:

TypeScript
async function withRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const isRateLimit = error.message?.includes("rate limit");
      const hasRetriesLeft = attempt < maxRetries - 1;
      if (isRateLimit && hasRetriesLeft) {
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        await new Promise((resolve) => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
}


// Usage
const result = await withRetry(() =>
  repo.hypercerts.create({ /* ... */ })
);

Privacy

Records are public by default

All ATProto records are public. Anyone can read records from any PDS. Never store sensitive personal data in hypercert records.

Include in records:

  • Public work descriptions (e.g., "Planted 500 trees in Borneo")
  • Aggregated impact metrics (e.g., "Reduced CO₂ by 50 tons")
  • Public contributor identities (DIDs, handles)
  • Links to public evidence (URLs, IPFS CIDs)

Keep off-protocol:

  • Personal contact information (email, phone, address)
  • Proprietary methodologies or trade secrets
  • Participant consent forms or private agreements
  • Raw data containing PII (personally identifiable information)

Store sensitive data in a private database and reference it by ID if needed.

Deletion and GDPR

You can delete records from your PDS at any time. However:

  • Indexers (like Hypergoat) may cache records and take time to update
  • Other users may have already fetched and stored copies
  • The deletion event itself is visible in your repository history

If you accidentally publish PII, delete the record immediately and contact indexer operators to request cache purging.


Authentication in production

Use OAuth for production applications. OAuth lets users authorize your app without sharing credentials. See the Quickstart for the SDK OAuth setup and the ATProto OAuth spec for the full protocol details.

Never commit credentials to version control. Use .env files (added to .gitignore) for local development and secret management tools (Vercel env vars, AWS Secrets Manager, etc.) for production.

Terminal
# .env (never commit this file)
ATPROTO_CLIENT_SECRET=your-client-secret-here
TypeScript
const secret = process.env.ATPROTO_CLIENT_SECRET;
if (!secret) throw new Error("ATPROTO_CLIENT_SECRET not set");

Going live checklist

Before deploying to production:

  • [ ] Tested record creation and retrieval — Created and read back at least one hypercert successfully in a test environment.
  • [ ] Validated records against lexicon schemas — All required fields present, datetimes in ISO 8601, strong references include both URI and CID.
  • [ ] Verified your DID and handle — Confirmed your DID resolves correctly and your handle matches your intended identity.
  • [ ] Stored credentials securely — No secrets in source code. Environment variables or secret management in production.
  • [ ] Reviewed records for accidental PII — No sensitive personal data in any record fields.
  • [ ] Tested cross-PDS references — If your app references records from other PDSs, verified those references resolve correctly.
  • [ ] Implemented error handling — Your app handles validation errors, rate limits, and network failures gracefully.

See also