SDK overview
Two SDKs, TypeScript and Python, with the same four records calls and the fingerprint helper. What each one needs, how errors and retries reach your agent's code, and where the two differ.
Both SDKs wrap the same records API and speak in the same words: record is your id for a record, entry is an entry's number counted from 1, as every page counts it, and the SDK maps them to the wire's entityId and seq for you. On the wire seq counts the same positions from 0; the SDK converts at its boundary, so you never send a seq, and an entry below 1 is refused before anything is sent. The history file and the webhook payloads keep the wire's seq; add 1 to get the entry. If your code passed the wire's numbers to an SDK before 0.11.0 or 0.4.0, add 1.
Put the calls where your system already logs what an agent did, such as the harness or the tool router, so the key stays with your code and the agent never holds it.
| TypeScript | Python | |
|---|---|---|
| Package | @pacspace-io/sdk on npm | pacspace-sdk on PyPI |
| Version these docs are written to | 0.12.0 | 0.5.0 |
| Needs | Node.js 20.19 or later | Python 3.10 or later; the calls on these pages use only the standard library |
| Host | https://app.pacspace.io, chosen by the key | The same |
| License | MIT | MIT |
What both carry
| Call | What it does | Page |
|---|---|---|
fingerprint(...) | Fingerprints a file, bytes, or a stream where it lives. Nothing leaves. | Fingerprint a file |
records.emit(...) | Writes one entry. | Write an entry |
records.history(...) | Reads the record's history file. | History |
records.receipt(...) | Reads one committed entry's receipt. | Receipts |
records.check(...) | Checks a history from your code. | Check |
webhooks.verify(...), webhooks.verifyFromHeaders(...) | Checks a webhook delivery's signature before you act on it. | Signature verification |
Sharing a record is a plain HTTP call in both languages today; see Share a record.
Where they differ
The check. The TypeScript records.check runs the whole check in your process: every seal, the order of entries, and the file's own fingerprint. The Python records.check compares the files and fingerprints you hold with the committed ones and reads each entry's status; it does not recompute the seals. From Python, hand the history file to the standalone checker, npx @pacspace-io/check history.json, for the whole check.
Chosen fields. TypeScript has records.disclose, which builds a file that shows only the fields you choose of chosen entries. Python has verify_record_link_copy_v1 and verify_record_disclosure_v1, which check such a file's shown fields against the record's seal history without recomputing the seals.
Return values. TypeScript returns typed objects in the SDK's words. Python returns plain dicts carrying the wire's field names, so a Python answer has entityId and seq where the TypeScript one has typed fields.
Webhook middleware. TypeScript has webhooks.middleware() for Express-style servers. Python has verify and verify_from_headers and leaves the framework to you.
A write that keeps failing. TypeScript holds it and keeps sending it in the background; Python raises the error. See below.
Errors
Both raise one family, PacSpaceError, with the HTTP status in statusCode (status_code in Python), a code, and the API's message. Which code you read depends on the answer:
| The API answers | The SDKs raise | code |
|---|---|---|
| 400 with a code, 403, 409, 413, 422, or 500 | PacSpaceError | The API's code, exactly as the registry names it, such as RECORD_TITLE_TOO_LONG. |
| 400 with no code: a body the route could not read | ValidationError | The SDK's own. |
| 401 | InvalidApiKeyError | The SDK's own. |
402, such as PLAN_LIMIT_REACHED | InsufficientCreditsError | The SDK's own. |
404, such as RECORD_NOT_FOUND | NotFoundError | The SDK's own. |
412, such as RECORDS_CONTRACT_NOT_READY | ContractNotDeployedError | The SDK's own. |
429 RATE_LIMIT_EXCEEDED | RateLimitError, with the wait in retryAfter (retry_after) | The SDK's own. |
| 503 | ServiceUnavailableError | The SDK's own. |
| No answer: the host could not be reached, or the request timed out | PacSpaceError with statusCode 0 | The SDK's own. |
Where the SDK raises its own class, the class names the case and message carries the API's sentence; the API's code is not passed on. The SDKs also raise ValidationError themselves, before anything is sent: for an entry below 1, and for a blinding file that does not belong to the bytes. records.check never raises on a failed check; the result names what failed. IDEMPOTENCY_PENDING is returned, never raised.
Retries and timeouts
Both SDKs send a request again after a network error or a timeout, or an answer of 408, 429, 500, 502, 503, or 504: twice by default (maxRetries, max_retries). A 429 or a 503 that names its wait in retryAfterSeconds is sent again after exactly that wait; otherwise the wait grows between tries. Each request times out after 30 seconds (timeout, in milliseconds). In TypeScript, records.history and records.disclose are sent once, without these retries, and a network failure there reaches you as the runtime's own error.
When a write still fails after its retries, TypeScript holds it in memory and keeps sending it in the background (bufferWrites, on by default), so the records.emit promise settles only when the write lands or the SDK gives up. An agent loop that must not wait on it can set bufferWrites: false and handle the error. Python raises the error after its retries.
A write is safe to send again because one idempotency key is one write: a retry of a finished write returns the original answer, and a retry while the first is still running returns IDEMPOTENCY_PENDING with retryAfterSeconds, which the SDKs return rather than raise.
What else is in the package
Both SDKs also expose balance and customers, used by another PacSpace product. They are outside these docs.