1. Start here
If your goal is simply to upload sales or review findings, use your workspace. For file limits, mapping, before/after review and Scoutly Inbox, follow the step-by-step import guide. Choose the API when a trusted accounting system, reporting script, or private integration needs to read those results automatically.
- Confirm the organization and plan. Your key belongs to the organization selected when it is created.
- Create a read-only key first. Have an organization owner or administrator open Settings → API keys.
- Store the secret outside your code. Use your integration’s encrypted secret configuration or a password manager.
- Make one GET request. Use /me to check your organization and access before reading records.
- Review the dates and missing facts. A successful connection does not mean the underlying tax or trade conclusion is complete.
This guide is public documentation, not a live console. It never asks you to paste your secret into a documentation form and does not send requests on your behalf.
2. Plans, roles, and permissions
| Plan | API access | Shared request limit |
|---|---|---|
| Sample Scan | Not included | Not applicable |
| Starter | Not included | Not applicable |
| Growth | Included when account access is valid | 60 requests/minute |
| Team | Included when account access is valid | 180 requests/minute |
| Enterprise | Requires an explicit server-configured contract | Contracted, up to 600 requests/minute; shown in API Keys settings |
Your key must pass all checks: it must be valid and not revoked, its creator must still have the required organization access, the account must have an eligible API entitlement, and the requested method and endpoint must be supported. Paying for a tier does not grant staff or Ops privileges.
- read — required for every key
- Allows only the documented GET endpoints below. This includes existing report downloads where the creator has export permission. It does not expose every page or GET route used internally by the website.
- write — optional, in addition to read
- Adds only POST /api/v1/obligations. This starts background evaluation work and can update saved obligation results. It does not grant general record-editing powers.
Not available through API keys: creating or editing products, transaction uploads, new research runs, export creation, findings changes, account or membership management, key management, billing, integrations, support conversations, or Ops administration. Use the appropriate signed-in screen for these workflows. Endpoints used by that screen are not automatically customer API endpoints.
If access becomes unavailable after a downgrade or account-status change, an authorized owner or administrator can still review and revoke existing keys in Settings. A billing change does not erase stored customer records.
3. Create and safely store a key
- Sign in and select the intended organization. Open Settings → API keys.
- Read the API access panel. If access is unavailable, ask your owner or billing administrator to check the plan and account status. Do not repeatedly generate credentials to solve a plan error.
- Enter a descriptive name such as “Accounting reporting integration.” Leave obligation re-evaluation unchecked unless it is needed.
- Choose Create key. An organization can have up to 20 non-revoked keys. Save the complete secret immediately. It begins with
obk_. The shorter prefix in the key list is just an identifier and will not work as a credential. - In your integration host, create a secret environment variable named
OBLIGORA_API_KEY. Paste the full value there, not into code, a public web form, or a support message. - After saving it, choose “I’ve saved it — hide key.” The full secret cannot be recovered from the key list; only its hash is stored server-side.
Keys do not currently have an automatic expiry date. Review and explicitly revoke keys you no longer need. They stop working when revoked or when their creator loses the necessary membership/role or the organization loses eligible subscription access. Use one named key per integration so you can disable one connection without interrupting the others. Creating more keys does not increase your organization’s shared rate limit. This is your Obligora organization key, not an OpenAI key. An OpenAI key belongs to a different service and cannot connect your organization here. It is also not your Google sign-in password or a Stripe or Firebase credential.
What if I lose the secret or the creation request times out?
Review the key list first. A connection failure may occur after the server created a key. Revoke any newly created key whose secret you did not receive, then create a replacement.
Revocation is permanent for that key. It stops future authentication but does not delete records or cancel work that was already accepted. Update your integration with the replacement secret before removing a working key when continuity matters.
4. Make your first request — read only
Use a trusted computer. The commands below read the secret from a hidden prompt, so it is not typed into a command saved in your shell history. It will exist in this terminal’s process environment while you test. Close the terminal or clear the variables afterward.
The HTTP request is GET /api/v1/me. It checks your organization, key scopes, and API access without reading customer records or starting a calculation. No body, organization ID, login cookie, or idempotency key is needed.
Windows: PowerShell
Open PowerShell, paste this block, and enter your secret at the hidden prompt. The examples use Invoke-RestMethod, avoiding Windows’ historical curl alias. Lines are complete commands; there are no fragile continuation backticks.
# Windows PowerShell: the prompt hides the secret.
$secureKey = Read-Host 'Paste your API key' -AsSecureString
$keyPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey)
try {
$env:OBLIGORA_API_KEY = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($keyPointer)
} finally {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($keyPointer)
$secureKey.Dispose()
}
$env:OBLIGORA_API_BASE = 'https://www.obligora.com/api/v1'
$apiHeaders = @{ Authorization = "Bearer $env:OBLIGORA_API_KEY" }
$result = Invoke-RestMethod -Method Get -Headers $apiHeaders -Uri "$env:OBLIGORA_API_BASE/me"
$result | ConvertTo-Json -Depth 6PowerShell converts JSON into objects. The final command displays the organization, access, key prefix and scopes, and documentation links. A non-success HTTP status raises an error; use the troubleshooting table below rather than retrying with a new key immediately.
macOS / Linux: Bash and curl
Use Bash with curl. A backslash at the end of a line continues the same command; do not add spaces after it. If your curl does not support --fail-with-body, use --fail --show-error instead.
# Bash: paste the key at the hidden prompt, not into this command.
printf 'Paste your API key: '
IFS= read -r -s OBLIGORA_API_KEY
printf '\n'
export OBLIGORA_API_KEY
export OBLIGORA_API_BASE='https://www.obligora.com/api/v1'
curl --fail-with-body --include \
--header "Authorization: Bearer $OBLIGORA_API_KEY" \
"$OBLIGORA_API_BASE/me"The --include option prints response headers before the JSON. Look for HTTP 200, then check the organization name and key scopes. Do not use verbose HTTP tracing with a real secret: it can print the Authorization header.
Success means the connection worked. Confirm that organization.name identifies your intended workspace and key.scopes includes read. The returned key.prefix is an identifier, not a usable secret. access.requestsPerMinute is your current organization request limit.
{
"organization": {
"id": "12345678-1234-4234-8234-123456789012",
"name": "Example organization"
},
"access": {
"planId": "growth",
"requestsPerMinute": 60,
"administratorOverride": false
},
"key": {
"id": "23456789-2345-4345-8345-234567890123",
"prefix": "obk_example",
"scopes": [
"read"
]
},
"links": {
"documentation": "https://www.obligora.com/docs/api",
"openapi": "https://www.obligora.com/api/v1/openapi.json"
}
}Opening /api/v1 in your browser shows public discovery links without a key. Opening a data endpoint will not attach your integration’s bearer token. Use the documented authenticated request instead of putting the secret into the URL.
5. Understand the results before automating
After checking /me, use GET /obligations to read saved results. An empty evaluations array is valid when no evaluations have been saved; it is not proof that you have no obligations.
curl --fail-with-body \
--header "Authorization: Bearer $OBLIGORA_API_KEY" \
"$OBLIGORA_API_BASE/obligations"{
"summary": {
"counts": {
"crossed": 0,
"possibly_crossed": 0,
"approaching": 0,
"insufficient_information": 0,
"registered": 0,
"clear": 0,
"not_applicable": 0
},
"presence": 0,
"total": 0,
"evaluatedAt": null,
"asOf": null
},
"evaluations": []
}- Dates:
asOfis the date evaluated;evaluatedAtis when the engine ran. Saved results do not become current merely because you fetched them now. Read the evidence’s source dates too. - Amounts: money and threshold amounts are decimal strings, such as
"100000.0000000000". Preserve them as decimals; do not use binary floating-point arithmetic for accounting calculations. Currency codes belong with every amount. Never add USD and CAD without an explicit conversion basis. - Uncertainty:
salesKnownandsalesPossibleare bounds. ChecksalesUnboundedandmissingFacts; a displayed possible amount is not always a firm upper limit. Null is not zero and unknown is not false. - Provenance: keep
ruleVersionId,engineVersion,evidence, evaluation date, and relevant record IDs with your downstream explanation. - Counts: multiple rules can apply to one jurisdiction. Do not label an evaluation-row count as a country count.
What do obligation statuses mean?
- clear
- No trigger identified by this evaluation from the available facts; not a universal legal clearance.
- approaching
- Activity is nearing a supported threshold; inspect the applicable period and rule.
- possibly_crossed
- Unknown facts could change whether the threshold is met. Resolve those facts before acting.
- crossed
- Known qualifying activity appears to meet the rule’s threshold. This is an indicator for professional review.
- registered
- A registration has been recorded; this does not certify that filings or tax treatment are correct.
- insufficient_information
- Required information is missing or unsupported; no safe definitive result is available.
- not_applicable
- The evaluated rule does not apply to the facts considered; another rule may still apply.
This is a research estimate and not a legal, tax, or customs determination. It's a good idea to review the result with your accountant, tax advisor, or other qualified professional before registering, filing, or changing tax treatment. It's a good idea to have your customs broker review the classification before acting.
Read the calculation and source methodology →6. Supported endpoint reference
Append each path to https://www.obligora.com/api/v1. Every endpoint in this reference requires the Authorization header. The public discovery and OpenAPI documents linked above do not. In paths containing {id}, substitute the actual UUID from a previous response. Braces are documentation notation, not part of the request.
Successful JSON responses contain the fields described below directly—there is no universal data wrapper. Downloads are files. Unknown endpoints and website-only actions are not supported for keys.
GET /meCheck your connection and organization
- Inputs
- No query parameters or body. Send the organization API key in the Authorization header.
- Response
- JSON: organization (id, name), access (planId, requestsPerMinute, administratorOverride), key (id, prefix, scopes), and links (documentation, openapi). key is null for a website-session request.
- How to use it
- Start here to check the organization and permitted access without returning the full key, personal profile, billing details, settings, or customer records. administratorOverride reflects verified platform-operator access, not an ordinary organization administrator role. Membership, scopes, endpoint restrictions, and rate limits still apply.
GET /obligationsRead saved obligation results
- Inputs
- No query parameters are needed. The live query parameter is not available with an API key. Use POST below to request a new evaluation.
- Response
- JSON: summary and evaluations. summary contains counts, presence, total, evaluatedAt, and asOf.
- How to use it
- Returns the latest saved evaluation rows, including status, jurisdictionId, asOf, evaluatedAt, money strings, missingFacts, evidence, ruleVersionId, and engineVersion. Several rule lineages can concern one jurisdiction; summary.total counts evaluation rows, not necessarily distinct countries.
GET /transactionsRead your economic event ledger
- Inputs
- limit: 1–200 (default 50); offset: 0 or greater (default 0). Optional from/to dates (YYYY-MM-DD, inclusive), jurisdiction (for example US-CA, GB, or none), kind (sale/refund/adjustment), marketplace (name), status (effective/reversed/superseded; default effective), batchId (UUID).
- Response
- JSON: events, total, limit, offset, totals.
- How to use it
- events is the current page. total counts matching events across pages. totals describes the same filter, not just this page: events, sales, refunds, withoutJurisdiction, first, last, and byCurrency. byCurrency keeps currencies separate and contains gross, refunds, and net decimal strings. Refunds are signed negative; do not subtract them a second time.
GET /productsRead catalog variants
- Inputs
- page: starts at 1 (default 1); pageSize: 1–200 (default 50); q: search text; watched: 1 for watched, 0 for unwatched; omit watched for both.
- Response
- JSON: rows, total, page, pageSize.
- How to use it
- Each row represents a variant/SKU, not an entire product. It includes productId and variantId. Use productId—not the SKU or variantId—for the product-detail endpoint.
GET /transactions/{id}Read one economic event and its items
- Inputs
- Replace {id} with an event UUID returned by /transactions. No query parameters or body.
- Response
- JSON: event, items (up to 200), and itemsTruncated (boolean).
- How to use it
- event uses the same fields as the ledger listing. Items are ordered by ID; itemsTruncated means more than 200 items exist. There is no item-pagination endpoint. Amounts and quantities remain decimal strings; unknown facts remain null. Missing or inaccessible events return 404. API keys cannot edit transaction facts.
GET /products/{id}Read a product dossier
- Inputs
- Replace {id} with a productId UUID returned by /products. No query parameters are needed.
- Response
- JSON: product, variants, facts, sourcing, runs, and truncated (variants, facts, sourcing, runs booleans).
- How to use it
- At most 200 variants, 200 facts, 200 sourcing records, and 50 recent saved research runs. Child records cover only returned variants; when variants are truncated, child coverage is partial even if another flag is false. Runs exclude internal inputs, errors, and provider costs. Use in-app review or relevant exports for incomplete history; there is no detail pagination. Reading does not start research or confirm a customs classification.
GET /findingsRead the investigation queue
- Inputs
- page: starts at 1 (default 1); pageSize: 1–200 (default 50). Optional status, domain, severity, jurisdiction, type, owner, sort, dir. See filter details below.
- Response
- JSON: findings, total, page, pageSize, counts.
- How to use it
- total is the filtered count; counts is the organization-wide breakdown by status, not the filtered subtotal. Findings are indicators for investigation, not proof of a registration obligation or realized savings.
GET /findings/{id}Read one finding and its evidence
- Inputs
- Replace {id} with a finding UUID returned by /findings.
- Response
- JSON: finding.
- How to use it
- The finding includes comments, history, outcomes (up to 200 each), rule information, and truncated flags for comments/history/outcomes. Check every flag; use in-app review or relevant exports for incomplete history. There is no detail pagination. Keys cannot change status or add comments.
GET /exportsList existing report exports
- Inputs
- No pagination parameters. Returns up to 100 most recently created exports.
- Response
- JSON: exports.
- How to use it
- Each export includes its id, kind, format, status, manifest, creation and expiry dates, and downloadUrl when ready. Create a report in the Reports screen first; API keys cannot create exports.
GET /exports/{id}Check an existing export
- Inputs
- Replace {id} with an export UUID returned by /exports.
- Response
- JSON: export.
- How to use it
- Check status and expiresAt before downloading. A null downloadUrl means no download is available yet or the export has expired. Export status is separate from job status.
GET /exports/{id}/downloadDownload a ready report
- Inputs
- Use the same export UUID. Send the Authorization header for the download too.
- Response
- File bytes, not a JSON envelope. Content-Type identifies CSV or JSON; Content-Disposition supplies a filename; X-Content-Sha256 is the file hash.
- How to use it
- Returns 404 when missing, not ready, expired, or inaccessible. Save files in a private location. downloadUrl is a relative authenticated path, not a public sharing link.
GET /jobs/{id}Check background work
- Inputs
- Replace {id} with the jobId UUID returned by POST /obligations.
- Response
- JSON: job, containing id, type, status, createdAt, startedAt, finishedAt, and message. Times or message may be null.
- How to use it
- Only jobs belonging to the key’s organization are accessible. Internal job payloads, results, and raw errors are not returned. Read saved obligations after the evaluation job succeeds.
POST /obligationsRequest an obligation re-evaluation
- Inputs
- Requires read + write scopes, Content-Type: application/json, and an Idempotency-Key header. Body: {} or {"asOf":"2026-08-31"}. The date is an illustrative example, not a recommended reporting period.
- Response
- 202 JSON: jobId and queued. A 202 means accepted, not finished.
- How to use it
- Evaluates your existing organization data; it does not upload transactions, start classification research, register for tax, or file returns. Additional body fields are rejected. One new API evaluation per organization per minute.
7. Filters and pagination
Query parameters come after ?; join multiple parameters with &. Quote the entire URL in your shell. For free-text filters, use your programming language’s URLSearchParams or equivalent encoding rather than concatenating untrusted text.
curl --fail-with-body \
--header "Authorization: Bearer $OBLIGORA_API_KEY" \
"$OBLIGORA_API_BASE/transactions?limit=50&offset=0&status=effective"- Transactions: start with limit=50 and offset=0. Read
events; the next offset is 50, then 100. Stop when the page is empty or the accumulated offset reachestotal. Totals cover all matching events, not only the current page. - Products and findings: start with page=1 and pageSize=50, then increase page by one. Products return
rows; findings returnfindings. Use the returned total and page size to stop. - Obligations and exports: these endpoints do not offer the same pagination controls. Exports returns at most the latest 100 rows. Do not assume a cursor or unsupported parameter works.
Unsupported or repeated query parameters return 422. Use each documented filter only once. Dates must be real YYYY-MM-DD calendar dates, with from no later than to. Pagination must be whole numbers: page starts at 1, offset at 0, and both are at most 2147483647; limit and pageSize are 1–200. watched must be 0 or 1. Query values are at most 200 characters. Detail endpoints accept no query parameters. The unsupported live obligation query returns 403.
Detail responses are bounded: transaction items stop at 200; product variants, facts, and sourcing stop at 200 each, and research runs at 50; finding comments, history, and outcomes stop at 200 each. Check itemsTruncated or every truncated flag. When product variants are truncated, all child arrays cover only those returned variants, even if their own flags are false. No detail pagination is available. Use in-app review or relevant exports when history is incomplete; do not treat these responses as complete bulk history.
These lists are not frozen snapshots across requests. Concurrent imports or edits can shift page boundaries. For consistent downstream processing, de-duplicate by the returned record IDs, use the same filters throughout a run, and use a saved report when you need a fixed evidence packet.
All finding filters
- status
- new, investigating, awaiting_information, resolved, dismissed
- domain / severity
- domain: trade, tax, data. severity: info, low, medium, high, critical.
- jurisdiction / type / owner
- jurisdiction: a code such as US-CA or GB. type: an exact finding type from your data. owner: a user UUID or unassigned.
- sort / dir
- sort: created (default), severity, amount. dir: desc (default) or asc. Amount sorting is not a currency conversion or a cross-currency valuation.
The response’s counts remains an organization-wide status breakdown even when filters narrow the listed findings.
8. Request an obligation re-evaluation
This is the one supported API write action. It uses existing transactions and recorded facts, queues work, and returns immediately. It does not upload missing data or perform new classification research.
Before sending it, use a key with read + write, decide whether you want the current date or a specific reporting date, and record one unique request identifier. The body is either {} or {"asOf":"2026-08-31"} (illustrative date). Dates must be real YYYY-MM-DD calendar dates. Do not send additional fields or an organization ID.
Idempotency-Key is required: 16–128 ASCII characters from letters, numbers, underscores, hyphens, periods, and colons. A UUID is a convenient choice. It is not your API secret.
Bash: request work
# Pick a new identifier for ONE intended evaluation.
# Keep this value and the exact body when retrying that evaluation.
request_key="eval-$(date -u +%Y%m%dT%H%M%S)-$RANDOM"
request_body='{}'
curl --fail-with-body --include --request POST \
--header "Authorization: Bearer $OBLIGORA_API_KEY" \
--header "Idempotency-Key: $request_key" \
--header "Content-Type: application/json" \
--data "$request_body" \
"$OBLIGORA_API_BASE/obligations"PowerShell: request work
# Generate this ONCE for the intended evaluation; retain it for retries.
$requestKey = [guid]::NewGuid().ToString()
$requestBody = '{}'
$writeHeaders = @{
Authorization = "Bearer $env:OBLIGORA_API_KEY"
'Idempotency-Key' = $requestKey
}
$accepted = Invoke-RestMethod -Method Post -Headers $writeHeaders -ContentType 'application/json' -Body $requestBody -Uri "$env:OBLIGORA_API_BASE/obligations"
$jobId = $accepted.jobId
$accepted | Format-ListThe 202 response contains jobId and queued. Save the job ID. queued: false can mean the request reuses existing work; it does not mean the request failed. Check the job status to know what happened.
- Timeout or lost response: retry the same request with the same idempotency key and body. Do not regenerate the identifier each time.
- Same key, same canonical body: returns 202 with the same job ID. JSON formatting differences are not a new action.
- Same key, changed body: returns 409 instead of silently scheduling different work.
- Deliberately new evaluation: use a new key and respect the one-new-evaluation-per-minute organization limit.
Retry protection is retained while the job exists, for at least 14 days from submission. Routine cleanup may remove succeeded or cancelled jobs after 14 days from creation; reusing their old identifiers after deletion can create new work. Keep your own job ID and request record. Reusing an identifier is not a scheduling feature: a daily evaluation needs a new intentional request identifier each day. An as-of evaluation uses the knowledge available to that run; it is not a promise to reopen an original historical analysis unchanged.
9. Track the background work
Poll GET /jobs/{id} with the returned UUID. Start with a few seconds between checks, increase the interval if work is still waiting, and stop at a terminal status. Respect Retry-After when rate-limited and set an overall timeout in your integration. The API does not promise an instant completion time.
Check a job from your terminal
# Supply the actual UUID from the accepted response.
# This prompt takes a job ID, never your secret key.
read -r -p 'Job ID from the response: ' job_id
curl --fail-with-body \
--header "Authorization: Bearer $OBLIGORA_API_KEY" \
"$OBLIGORA_API_BASE/jobs/$job_id"# Uses $jobId from the accepted response above.
$jobResponse = Invoke-RestMethod -Method Get -Headers $apiHeaders -Uri "$env:OBLIGORA_API_BASE/jobs/$jobId"
$jobResponse | ConvertTo-Json -Depth 6- queued — waiting
- The job is waiting for a worker or its next eligible attempt. Poll later; do not create a replacement merely because it is queued.
- running — in progress
- A worker is processing the job. The request’s acceptance is not yet a completed evaluation.
- succeeded — finished
- Read GET /obligations for the saved results. Verify asOf and evaluatedAt: this endpoint returns the latest saved results, not an immutable result reserved exclusively for your job.
- dead — automatic attempts ended
- The job could not finish within its retry policy. Stop polling and ask an administrator to investigate using the job ID and request ID. Raw internal errors are intentionally not exposed.
- failed — failed status
- A failed status is also possible. It is not a successful evaluation. Stop polling after your integration’s timeout and ask an administrator to review the job before submitting new work.
- cancelled — stopped
- This job will not run to completion. Stop polling and review why it was cancelled before requesting replacement work.
Read the status from job.status. The job object contains id, type, status, createdAt, startedAt, finishedAt, and message; unused timestamps or message may be null. It does not expose internal payloads, results, or error traces. Export readiness is a separate workflow: check GET /exports/{id} for a ready status and a non-null downloadUrl before downloading. An export ID is not interchangeable with a job ID.
10. Rate limits are not billing meters
The Growth limit is 60 requests per minute; Team is 180. Every key in the same organization shares that limit. Enterprise uses its configured contract, up to 600 requests per minute. Verified platform operators have a 180-request minimum, retaining any larger eligible contract. An organization administrator role alone does not activate that override; membership, permissions, scopes, and request limits still apply. New API obligation evaluations have a separate one per minute organization limit. Reading or polling can still consume request capacity, even when it starts no new work.
X-Request-Id: the diagnostic request reference. Application errors repeat it in error.request_id. Rate headers below appear once the API-key rate bucket is checked; early authentication or access failures may omit them.X-RateLimit-Limit: the organization’s request allowance for the rate window.X-RateLimit-Remaining: remaining request capacity reported for that window. Concurrent integrations may consume it before your next request.X-RateLimit-Reset: the end of the current UTC-minute bucket as a Unix timestamp in seconds (not milliseconds). For retry timing, follow Retry-After on a 429 rather than guessing from your computer’s clock.Retry-Afteron HTTP 429: how long to wait before trying again. Use a small randomized delay in addition to the minimum wait when multiple workers are involved.
The operational rate-counter records are retained for two days. These are operational burst limits, not a per-request API charge. They do not change your separate monthly economic-event, entry-line, research-run, or report allowances. Reading saved results does not create new research. Retrying a job request with the same idempotency key does not create another intended evaluation.
Do not respond to a rate limit by generating extra keys or retrying in a tight loop. Share one request budget across your integrations, use pagination, avoid repeatedly downloading unchanged files, and queue your own requests when necessary.
11. What an error means and how to fix it
Most application errors use a JSON error object. Save the HTTP status, code, request ID, method, path, and approximate time. Some hosting or network failures can return a non-JSON response; check Content-Type and status before parsing.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request field",
"field": "asOf",
"request_id": "example-request-id",
"retryable": false
}
}| Status | Plain-language meaning | Next step |
|---|---|---|
| 401 | No usable authentication. | Check Bearer plus one space and the complete obk_ secret. A prefix, revoked key, missing environment variable, or credential for another service will not work. Do not send a Google token as an API key. |
| 403 | Authenticated access is not permitted. | Check plan eligibility, current account status, creator membership/role, scope, and the documented route list. Adding write does not unlock unsupported endpoints. Ask an owner to review access. |
| 404 | The record or route is unavailable to this key. | Use an ID returned for this organization. For exports, check readiness and expiry. Do not infer that a record belongs to another customer from a 404. |
| 405 | This HTTP method is not handled at that route. | Compare the method and path with the endpoint reference. An unsupported method that does have an internal handler may instead return 403 to an API key. |
| 409 | A request conflicts with existing work. | For idempotency conflicts, retry the original body with its original key. Use a new identifier only when deliberately requesting different work. |
| 413 | The request body is too large. | JSON request bodies are limited to 1 MiB. Obligation re-evaluation needs only an empty object or an asOf date, never a transaction file or a pasted report. |
| 415 | The request body has the wrong content type. | Send Content-Type: application/json for POST /obligations, with a JSON object body. |
| 422 | An input did not pass validation. | Read error.field and error.message. Check JSON syntax, real dates, permitted values, page ranges, and the required Idempotency-Key. Remove unsupported or repeated query parameters. Fix the input before retrying. |
| 429 | The organization is sending requests or new evaluations too quickly. | Wait at least Retry-After, then retry with backoff. Keep the same body and idempotency key for an uncertain write. Coordinate all keys in this organization. |
| 500 / 503 | The service or a required dependency could not complete the request. | Retry reads with bounded backoff. For a write with an uncertain outcome, preserve its idempotency key and body. If repeated, report the request ID and time without sharing secrets or private records. |
retryable: true describes whether retrying may help; it is not an instruction to retry indefinitely. Authentication, permission, and validation problems need correction. Keep a bounded retry count and surface persistent failures to a person.
I received HTML, a redirect, or a sign-in page instead of JSON
Check the exact base address and /api/v1 path, not an /app page. Use the canonical address at the top of this guide and the Authorization header. Do not forward secrets automatically to an unfamiliar redirect destination. Browser login and server API authentication are separate.
My response is empty, but I uploaded data
Confirm that the key belongs to the same organization as the upload, normalization/commit finished, and your filters match the data. Saved obligation results require a completed evaluation. Look at missingFacts and source/evaluation dates; a successful upload does not guarantee every jurisdiction can be evaluated.
12. Security checklist for your integration
- Run requests from a trusted backend or private automation runner, not browser JavaScript, a mobile app bundle, or a public notebook. There is no customer browser-CORS integration contract for these bearer keys.
- Store secrets in environment/secret configuration, never source code, query parameters, localStorage, analytics events, screenshots, or support messages. Never prefix a server secret with NEXT_PUBLIC_.
- Use HTTPS for hosted environments and the exact configured domain. Do not disable TLS verification or forward the Authorization header across untrusted redirects.
- Default to read-only and give each integration a recognizable name. Review the key list when staff access changes. Current creator permissions still limit every key.
- Keep downloaded reports and API data private. Your own integration must enforce who can see the data after retrieval.
- Log request IDs and outcomes, not Authorization headers, full secrets, private financial responses, or full customer records.
- Use separate environments and organization data for tests. These examples are not a dry-run mode: a permitted POST affects the organization tied to the supplied key.
- When a key is no longer needed, revoke it in the signed-in settings screen. Revocation is not available through that same bearer key.
For this terminal session, remove the environment secret when finished. Also clear header variables that contain it and close the terminal. This removes the current process copies, not copies you intentionally saved in a password manager or server secret store.
unset OBLIGORA_API_KEYRemove-Item Env:OBLIGORA_API_KEY -ErrorAction SilentlyContinue
Remove-Variable apiHeaders, writeHeaders -ErrorAction SilentlyContinue13. Plain-language API glossary
These definitions are for customers and their developers. They do not expose or grant access to staff-only Ops tools.
- API
- A controlled way for one piece of software to ask another for information or request a supported action. You can keep using the website without an API integration.
- Endpoint
- An address for one operation. The method matters: GET /obligations reads saved results; POST /obligations asks for new background work.
- Base URL
- The shared beginning of every API address. Add one endpoint path to it. Do not add /api/v1 twice.
- GET / POST
- GET reads available data. POST requests a supported action. In this API release, a key can POST only an obligation re-evaluation.
- API key / bearer token
- A secret credential for software. “Bearer” means anyone holding it can attempt to use its permitted access; it is not a username or a separate password.
- Organization / tenant
- Your company’s isolated workspace. A key is bound to one organization; sending a different organization ID cannot switch it to someone else’s workspace.
- Scope
- The operations a key may attempt. read is required; write adds obligation re-evaluation only. A scope cannot bypass your plan or the creator’s current permissions.
- Header
- Request metadata sent alongside the URL. Authorization carries the secret; Idempotency-Key identifies one intended re-evaluation. Neither belongs in the URL.
- JSON
- A structured text format with named fields. Strings use double quotes. {} means an empty object, not an omitted request body.
- UUID
- A unique record identifier returned by the API, such as a product ID or job ID. Copy the returned value; do not invent one or substitute a product name.
- Pagination
- Reading a long list a page at a time. Transactions use limit/offset; products and findings use page/pageSize. These are different conventions.
- Job / queue
- Background work that can continue after the request returns. Queued means waiting for a worker, not that the result is ready.
- Idempotency
- Retry protection for one intended action. Reusing the same key and body returns the same job; it is different from the secret API key.
- Request ID
- A diagnostic reference for one HTTP request. Save X-Request-Id or error.request_id when asking for help, without sharing your secret or private response body.
- Rate limit
- A short-term speed limit shared by an organization’s integrations. It is not a monthly research allowance and does not mean you have been charged.
- As-of date / evaluated-at time
- asOf is the date being evaluated. evaluatedAt is when the calculation ran. Neither means the government source was fetched just now.
- Null / unknown
- A missing or unavailable fact. Do not convert null to zero, false, a safe status, or a confirmed legal conclusion.
- Revoked
- Permanently disabled for future authentication. Existing records and already accepted jobs remain; replacing a key does not undo them.
14. What to give your developer
Send this guide and the organization’s API base address. Tell them which records to read, how often you need updates, and whether a deliberate obligation re-evaluation is required. Provide the secret only through your approved secret-sharing channel—not in the same email, ticket, or specification.
// Server-side JavaScript only; never ship the key to browser code.
const apiKey = process.env.OBLIGORA_API_KEY;
if (!apiKey) throw new Error('Configure OBLIGORA_API_KEY in your server secret store');
const response = await fetch('https://www.obligora.com/api/v1/me', {
headers: { Authorization: 'Bearer ' + apiKey },
redirect: 'error',
});
if (!response.ok) {
throw new Error('API HTTP ' + response.status + '; request ' + response.headers.get('x-request-id'));
}
const { organization, access, key } = await response.json();
// Check the organization and scopes before reading records.
// Keep private responses out of shared logs.- Confirm the integration uses only the documented endpoint allowlist and never tries to select a different tenant with a client-supplied organization ID.
- Check response status and shape before processing; preserve decimal strings, dates, rule identifiers, and uncertainty.
- Test pagination, an empty result, a revoked key, loss of entitlement, 429 backoff, and idempotent retries before scheduling unattended work.
- Agree who will review failed or dead jobs, and what happens when an export expires or a source becomes stale.
For a problem report, share the request ID, job ID if applicable, HTTP status, endpoint path, approximate time and timezone, and a short description of the expected behavior. Do not include the secret or unnecessary customer data. Start with your organization administrator for access or billing issues, or contact support from your signed-in workspace.