Ouruka ID Service
Ouruka is a REST API service for generating unique, structured IDs. Instead of building your own counters, date logic, and uniqueness guarantees, you define a schema (e.g. ORD-{DATE:YYYY}-{COUNTER:6}) and fetch ready-made IDs via API — sequential, collision-free, even under heavy load.
- Base URL:
https://ouruka.com - Authentication: API key in the header
X-API-KEY - API reference:
/swagger/(machine-readable, interactive)
Typical Use Cases
- PIM: Consistent article and product numbers across multiple channels.
- ERP: Document, order, and delivery note numbers in a uniform format.
- E-Commerce: Order, customer, and transaction numbers.
- Logistics: Shipment numbers, pallet IDs, shipping labels.
- Healthcare: Patient record, sample, and process numbers.
- Manufacturing: Serial, batch, and quality IDs.
The rest of this documentation walks you through authentication, schemas, all placeholders, the add-on services (UID, Keyify, Mappings), and a hands-on example library.
Quickstart
A few steps to your first generated ID.
Step 1 — Get an API Key
Your account (tenant) is provisioned by Ouruka. When you sign up, you receive an api_key once. This key identifies your tenant and is required for all requests — keep it safe (see Authentication).
Step 2 — Create a Schema
A schema defines the format of your IDs. You specify which building blocks (date, counter, fixed text, variables) make up an ID.
curl -X POST https://ouruka.com/v2/schemas \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"name":"order","pattern":"ORD-{DATE:YYYY}{DATE:MM}-{COUNTER:5}","start_value":1}'
This produces IDs in the format ORD-202601-00001.
Step 3 — Generate an ID
Use the schema name to fetch the next ID. Ouruka ensures every ID is unique.
curl -X POST https://ouruka.com/v2/generate/order -H "X-API-KEY: $KEY"
{ "id": "ORD-202601-00001" }
Each subsequent call returns the next number (…-00002, …-00003, …).
🔧 Under the hood: Uniqueness is guaranteed by the server, not your code. The counter is incremented atomically, so even thousands of concurrent requests from multiple systems will never receive the same number twice. You don't need to worry about locks or race conditions — a single
POSTper ID is all it takes.
Try it interactively: https://ouruka.com/swagger/ — click "Authorize", enter your API key, and test all endpoints directly in your browser.
Authentication
All endpoints that read or write data require a valid API key in the HTTP header:
X-API-KEY: <your API key>
Exceptions (accessible without a key): GET /health and GET /v2/changelog.
The API key uniquely identifies your tenant and controls access to all your schemas, counters, mappings, and history data. If the key is missing or invalid, the service responds with 401 Unauthorized.
🔧 Under the hood: There is no "forgot your key" flow — the key is both identity and permission, and Ouruka stores it only as a SHA-256 hash (the plain text exists only on your side). This means the key can never be "read back"; if it is lost, simply generate a new one via rotation. Internally there are two levels: the tenant key (your data) and a separate master key (Ouruka backend only, for administration) — the two are strictly separated.
Safe Handling
- Never store the API key in source code or versioned files.
- Use environment variables or a secret manager (e.g. AWS Secrets Manager, HashiCorp Vault, Kubernetes Secrets).
- Do not share it with third parties — anyone who knows the key can generate IDs in your name.
- Transmission is exclusively over HTTPS/TLS.
- Ouruka never stores keys in plain text, only as a cryptographic hash (SHA-256).
Key Rotation
If a key has been compromised — or just as a precaution — you can rotate it at any time. The new key is returned once, and the old one becomes immediately invalid:
curl -X POST https://ouruka.com/v2/me/rotate-key -H "X-API-KEY: $KEY"
{ "status": "rotated", "api_key": "…new key…",
"message": "The previous key is now invalid." }
Store the new key securely right away — it only appears in this one response.
Schemas & Placeholders
A schema is the template for an ID series — you define the format once and Ouruka delivers sequential IDs from that point on. A pattern consists of fixed text plus placeholders, e.g. ART-{DATE:YY}{DATE:MM}-{COUNTER:4} → ART-2603-0017.
🔧 Under the hood: A schema is bound to a tenant and has its own independent counter, its own history, and its own pattern. Schemas are immutable: once created, the pattern cannot be changed (
409 Conflicton a repeatedPOST). This is intentional — a running number series should not change its format mid-stream. To "change" a schema, delete it and create a new one (the counter then resets tostart_value).
Date Placeholders
Insert the current date automatically — useful for seeing at a glance when something was created.
| Placeholder | Result | Meaning |
|---|---|---|
{DATE:YYYY} |
2026 |
Four-digit year |
{DATE:YY} |
26 |
Two-digit year (saves characters) |
{DATE:MM} |
03 |
Month (01–12) |
{DATE:DD} |
18 |
Day (01–31) |
Mixed real-world examples:
| Industry | Pattern | Example ID |
|---|---|---|
| Logistics – Bills of lading | FB-{DATE:YYYY}-{COUNTER:6} |
FB-2026-000134 |
| Healthcare – Lab samples | LAB-{DATE:YYYY}-{COUNTER:5} |
LAB-2026-00891 |
| Fashion – Seasonal items | JKT-{DATE:YY}{DATE:MM}-{COUNTER:4} |
JKT-2603-0056 |
| Food service – Daily receipts | BON-{DATE:YYYY}{DATE:MM}{DATE:DD}-{COUNTER:4} |
BON-20260318-0047 |
| Events – Ticket codes | TICKET-{DATE:YY}-{COUNTER:5} |
TICKET-26-00042 |
🔧 Under the hood: The date is resolved at generation time in UTC (not when the schema is created). A daily prefix like
{DATE:YYYY}{DATE:MM}{DATE:DD}works great as a "bucket" for statistics and archiving. Order and separators are freely combinable.
Counters — {COUNTER:N}
The heart of every sequential number: an incrementing counter, left-padded with zeros. N is the number of digits ({COUNTER:4} → 0001, 0002, …).
🔧 Under the hood: The counter is incremented via an atomic Redis operation — even with many concurrent requests from different systems, each value is assigned exactly once (load test: 500,000 IDs, 0 duplicates).
Nis only the minimum width: if more numbers are generated than digits allow, the number grows ({COUNTER:4}simply outputs10000after9999). The optionalstart_value(default1) is set when creating the schema — ideal for continuing a number series from a legacy system seamlessly.
| Industry | Pattern | start_value | Example ID |
|---|---|---|---|
| E-Commerce – Orders | ORD-{DATE:YYYY}-{COUNTER:6} |
1 | ORD-2026-000342 |
| Pharma – Batches | CH-{DATE:YYYY}-{COUNTER:5} |
10000 | CH-2026-10047 |
| Accounting – Invoices (migration) | INV-{COUNTER:6} |
4711 | INV-004711 |
Auto-Reset (Interval Counters)
Many number sequences need to restart periodically from 1 (annual invoice numbers, daily tickets …). An appended interval does exactly that — automatically, without a cron job:
{COUNTER:N:daily|monthly|quarterly|yearly}.
| Pattern | Behaviour |
|---|---|
INV-{DATE:YYYY}-{COUNTER:5:yearly} |
2026: INV-2026-00001 … · 2027: INV-2027-00001 (reset) |
T-{DATE:YYYY}/{DATE:MM}-{COUNTER:4:monthly} |
July: T-2026/07-0001 · August: T-2026/08-0001 |
Q-{DATE:YYYY}-{COUNTER:3:quarterly} |
Q1: Q-2026-001 · Q2: restarts at 001 |
🔧 Under the hood: Technically the current period becomes part of the Redis counter key (
…:2026-03for monthly). When the period changes, a new key is created → the counter automatically resets tostart_value. Old period keys expire via TTL. Anystart_valueapplies at every reset (e.g. reset to1000instead of1).
Variables — {VAR1} to {VAR4}
Values you supply per request (e.g. country code, product group) — so your IDs carry context-specific information.
{VAR1}— insert value directly.{VAR1:-}/{VAR2:/}— value with a leading separator (-,/, …).
| Industry | Pattern | Input | Example ID |
|---|---|---|---|
| Logistics – Destination | SEND-{DATE:YYYY}-{VAR1:-}{COUNTER:6} |
var1=DE |
SEND-2026-DE-000071 |
| PIM – Category | ART-{VAR1:-}{DATE:YY}-{COUNTER:5} |
var1=ELEK |
ART-ELEK-26-00019 |
| Multiple variables | {VAR1}-{VAR2:-}{COUNTER:4} |
var1=DE, var2=B2B |
DE-B2B-0001 |
🔧 Under the hood: The separator only appears if the value is not empty. If a variable is empty, both the placeholder and the separator are cleanly omitted — no double separators (
ORD--0001). This lets a single pattern handle "with and without a suffix" simultaneously. Pass variables in the request body:{"var1":"DE","var2":"B2B"}.
Mappings — {MAP:table}
Translates a plain-text value into a code: you say "white", Ouruka inserts the stored code "101". This keeps code mappings in one place instead of scattered across every system.
| Pattern | Table color |
Request | Example ID |
|---|---|---|---|
{COUNTER:4}{MAP:color} |
white→101 | {"mappings":{"color":"white"}} |
0001101 |
ART-{MAP:color}-{MAP:size} |
white→101, S→202 | {"mappings":{"color":"white","size":"S"}} |
ART-101-202 |
🔧 Under the hood: Mapping tables are maintained per tenant (
POST /v2/mappings/{table}). When generating, you pass the plain-text key; Ouruka resolves it at ID-generation time. If the value is not in the table, you get400 Bad Requestwith a clear message — so a typo is caught immediately rather than producing a wrong ID. Ideal when PIM, ERP, and shop all need the same color or size code.
Schema type hash (Fingerprint)
Instead of a counter, a hash schema generates a deterministic code from defined fields — always the same output for the same input. Perfect for duplicate detection.
{ "name": "fingerprint", "type": "hash", "fields": ["iban","name"], "hash_length": 64 }
🔧 Under the hood: Ouruka sorts the fields alphabetically, concatenates them as
key=value|…, and computes a SHA-256 hash, truncated tohash_length(allowed:16,32,40,64). The result is deterministic and order-independent:{"iban":"DE..","name":"ACME"}produces the same hash as{"name":"ACME","iban":"DE.."}. When generating, all configured fields are required (otherwise400). Use case: same supplier/record → same fingerprint → recognisable as already known, without storing raw data in plain text.
Example Library
Concrete requests for every feature. All examples use https://ouruka.com as the base and $KEY as a placeholder for your tenant API key.
All protected endpoints require the header
X-API-KEY: $KEY.
1. Authentication
Every request (except /health and /v2/changelog) requires your API key in the header:
curl https://ouruka.com/v2/me -H "X-API-KEY: $KEY"
Missing or incorrect key → 401 Unauthorized.
2. Create a Schema
A schema is the template for an entire ID series: you define the pattern once, and every subsequent generation call returns the next ID from it. A schema is immutable after creation (to change it: delete and recreate) — this keeps a number series internally consistent.
curl -X POST https://ouruka.com/v2/schemas \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"name":"order","pattern":"ORD-{DATE:YYYY}-{COUNTER:6}","start_value":1}'
Response (with a preview of the first ID, without consuming the counter):
{ "name": "order", "pattern": "ORD-{DATE:YYYY}-{COUNTER:6}", "preview": "ORD-2026-000001" }
List & Delete Schemas
# All your schemas — including preview and last generated ID
curl https://ouruka.com/v2/schemas -H "X-API-KEY: $KEY"
# Delete a schema (also removes its counter and history entries)
curl -X DELETE https://ouruka.com/v2/schemas/order -H "X-API-KEY: $KEY"
🔧 Under the hood:
GET /v2/schemasreturnspreview,last_generated, andlast_generated_atper schema — useful for a dashboard. Since schemas are immutable, delete + recreate is the way to change a pattern; the counter then resets tostart_value.
3. Generate IDs
Simple Counter with Date
curl -X POST https://ouruka.com/v2/generate/order -H "X-API-KEY: $KEY"
{ "id": "ORD-2026-000001" }
The next call returns ORD-2026-000002, and so on.
Start Value (e.g. Legacy Migration)
Schema KND-{COUNTER:5} with "start_value": 1000 → first ID:
{ "id": "KND-01000" }
Counter with Auto-Reset (Interval)
Many number sequences need to restart periodically from 1 (e.g. annual invoice numbers). An interval counter does exactly that — automatically, no cron job or manual reset needed. Pattern INV-{DATE:YYYY}-{COUNTER:5:yearly} resets the counter each year:
| Time | Result |
|---|---|
| 2026 | INV-2026-00001, INV-2026-00002, … |
| 2027 | INV-2027-00001 (reset) |
Supported intervals: daily, monthly, quarterly, yearly.
Runtime Variables (with Optional Separator)
Schema ART-{VAR1}-{VAR2}-{COUNTER:4}:
curl -X POST https://ouruka.com/v2/generate/article \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"var1":"RED","var2":"XL"}'
{ "id": "ART-RED-XL-0001" }
Optional separator {VAR1:-}: if the variable is empty, both the placeholder and the separator are omitted.
Pattern ORD-{DATE:YYYY}{VAR1:-}{COUNTER:5} → with var1=DE: ORD-2026-DE-00001, without: ORD-2026-00001.
Attribute Codes from Mapping Table
Pattern {COUNTER:4}{MAP:color} (table color: white→101):
curl -X POST https://ouruka.com/v2/generate/article \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"mappings":{"color":"white"}}'
{ "id": "0001101" }
4. Hash Schema (Deterministic Fingerprint)
Generates the same hash from defined fields every time — ideal for duplicate detection.
curl -X POST https://ouruka.com/v2/schemas \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"name":"fingerprint","type":"hash","fields":["iban","name"],"hash_length":64}'
Generate (all fields required; order does not matter):
curl -X POST https://ouruka.com/v2/generate/fingerprint \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"iban":"DE12...","name":"ACME Ltd"}'
{ "id": "9f2c4a1b…" }
Same input → same 64-character hash every time. Allowed lengths: 16, 32, 40, 64.
5. Random IDs (UID)
No schema needed, purely random. Two variants, lengths 16 / 32 / 64 / 128 / 256 / 512.
curl -X POST https://ouruka.com/v2/uid/numeric \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{"length":16}'
{ "id": "8391027465018273", "length": 16, "type": "numeric" }
/v2/uid/alpha returns alphanumeric UIDs. UIDs are stored masked in history (only the last 5 characters visible).
6. Text to Key (Keyify)
Converts arbitrary text into a URL- and system-safe key (umlauts transliterated, special characters removed).
curl -X POST https://ouruka.com/v2/keyify \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"input":"Red Size – XL 42!"}'
{ "input": "Red Size – XL 42!", "key": "Red_Size_XL_42" }
7. Mapping Tables
With mapping tables you only need to know the plain-text value in your application (e.g. "white") — Ouruka inserts the stored code (e.g. "101") at generation time. This keeps code mappings centralised in one place instead of maintained separately in every connected system.
# Create/update entry
curl -X POST https://ouruka.com/v2/mappings/color \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"key":"white","value":"101"}'
# List all tables / entries of a table
curl https://ouruka.com/v2/mappings -H "X-API-KEY: $KEY"
curl https://ouruka.com/v2/mappings/color -H "X-API-KEY: $KEY"
# Delete a single entry / entire table
curl -X DELETE https://ouruka.com/v2/mappings/color/white -H "X-API-KEY: $KEY"
curl -X DELETE https://ouruka.com/v2/mappings/color -H "X-API-KEY: $KEY"
8. History (with Pagination)
curl "https://ouruka.com/v2/history?limit=50&offset=0" -H "X-API-KEY: $KEY"
[
{ "id": "ORD-2026-000002", "schema_name": "order",
"pattern_used": "ORD-{DATE:YYYY}-{COUNTER:6}",
"requested_at": "2026-07-26T10:15:03.421Z", "source_ip": "203.0.113.7" }
]
Page through: ?limit=50&offset=50 returns the next page.
9. Your Account & Limits
curl https://ouruka.com/v2/me -H "X-API-KEY: $KEY"
{
"package": "Orbit",
"limits": { "max_schemas": 5, "monthly_ids": 2000 },
"usage": { "schemas": 2, "ids_this_month": 341 }
}
10. Rotate API Key
Generates a new key; the old one becomes immediately invalid. Store the new key securely right away — it only appears once.
curl -X POST https://ouruka.com/v2/me/rotate-key -H "X-API-KEY: $KEY"
{ "status": "rotated", "api_key": "…new key…", "message": "The previous key is now invalid." }
11. Error Overview
| Situation | Status |
|---|---|
| Missing / invalid API key | 401 |
| Invalid input (e.g. disallowed UID length) | 400 |
| Limit reached (schemas or monthly quota) | 403 |
| Schema already exists (immutable) | 409 |
| Too many requests to public endpoints (60/min) | 429 |
Error responses are JSON, e.g.:
{ "error": "Schema limit reached (5/5). Delete an existing schema or upgrade your package." }
Random IDs (UID)
Sometimes you don't want a structured number — you just need a unique random code, e.g. for tokens, voucher PINs, or anonymous identifiers. That's exactly what UIDs are: purely random, no date or counter.
🔧 Under the hood: UIDs carry no semantics and require no schema. They are generated from cryptographic randomness; the longer, the smaller the (already tiny) collision probability. UIDs count towards the monthly ID quota and are stored masked in history (only the last 5 characters visible) — so a sensitive code never appears in full in the log.
Allowed lengths: 16, 32, 64, 128, 256, 512. (Other values → 400 Bad Request.)
Numeric UIDs — POST /v2/uid/numeric
Digits only (0–9) — great when people need to read the code over the phone or when it must go into a numeric field.
curl -X POST https://ouruka.com/v2/uid/numeric \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{"length":16}'
{ "id": "8391027465018273", "length": 16, "type": "numeric" }
| Use case | Length | Example |
|---|---|---|
| Voucher PIN | 16 | 7392048117263540 |
| Support ticket reference | 16 | 4410298356170022 |
| Barcode base | 32 | 82940173625481930274615098… |
Alphanumeric UIDs — POST /v2/uid/alpha
Letters + digits (a–z, A–Z, 0–9) — more compact and harder to guess at the same length.
curl -X POST https://ouruka.com/v2/uid/alpha \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{"length":32}'
| Use case | Length | Example |
|---|---|---|
| Session token | 32 | aX7kP2mRnQz9wLvBcT4eYdJ0hF6sN1uG |
| Anonymisation ID (study) | 16 | Kf3mX9pLrT2nVqBw |
| API / webhook secret | 64 | Zp3… (64 characters) |
🔧 Under the hood: The alphanumeric character space is significantly larger than the numeric one — a 16-character alpha UID is far "stronger" than a 16-character numeric UID. Rule of thumb: numeric for human-friendly codes, alphanumeric for technical keys/tokens. For security-critical secrets, prefer 64+ characters.
String Conversion (Keyify)
Keyify turns arbitrary text into a clean, system-safe key — for example, a product name into a technical identifier. Small, but handy.
🔧 Under the hood: Keyify is stateless (no schema, no stored ID) and is designed to generate consistent, repeatable keys from editorially maintained plain text — for example as database keys, internal codes, or as a building block before the actual ID generation. Same input always produces the same key.
POST /v2/keyify (authentication required).
What happens
- Spaces → underscore (
_) - German umlauts resolved:
ä→ae,ö→oe,ü→ue,Ä→Ae,Ö→Oe,Ü→Ue,ß→ss - Other special characters are removed
- Case is preserved
- Multiple consecutive underscores are merged
curl -X POST https://ouruka.com/v2/keyify \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"input":"Red Size – XL 42!"}'
{ "input": "Red Size – XL 42!", "key": "Red_Size_XL_42" }
Examples
| Input | Result |
|---|---|
Black Leather Jacket Size XL |
Black_Leather_Jacket_Size_XL |
Kühlschränke & Gefriergeräte |
Kuehlschraenke_Gefriergeraete |
Sports / Outdoor |
Sports_Outdoor |
Müller & Söhne GmbH |
Mueller_Soehne_GmbH |
Red Size – XL 42! |
Red_Size_XL_42 |
🔧 Under the hood: German umlauts and
ßare deliberately transliterated (ä→ae,ß→ss, …); other special characters are mapped to a base character where possible, otherwise removed. The key consists exclusively ofA–Z,a–z,0–9, and_. If you need lowercase URL slugs with hyphens, convert the result afterwards — Keyify is intentionally designed for technical keys (case-preserving, underscores).
History
Ouruka keeps a record of every generated ID — so you can always look up which IDs were generated when. Useful for audits, debugging, and completeness checks.
🔧 Under the hood: History is maintained per tenant and accessible via the REST API. It is a convenience / audit feature, not a long-term archive: retention period and maximum row count depend on your package, and older entries are cleaned up automatically. If you need IDs long-term, fetch them regularly and store them in your own system.
Stored per entry:
- the generated ID (UIDs are stored masked — only the last 5 characters visible),
- schema name and pattern used,
- generation timestamp (UTC),
- source IP.
Retrieval with Pagination
curl "https://ouruka.com/v2/history?limit=50&offset=0" -H "X-API-KEY: $KEY"
[
{ "id": "ORD-2026-000002", "schema_name": "order",
"pattern_used": "ORD-{DATE:YYYY}-{COUNTER:6}",
"requested_at": "2026-07-26T10:15:03.906071Z", "source_ip": "203.0.113.7" }
]
limit: number of entries (default 50, maximum 200).offset: skip the first N entries —?limit=50&offset=50returns the second page.- Sorted by: newest first.
🔧 Under the hood: For long lists, combine
limitandoffsetto page through results ("infinite scroll"): page 1 =offset=0, page 2 =offset=50, etc. Thesource_ipis the real client IP (fromX-Forwarded-Forbehind the reverse proxy); timestamps are microsecond-precise in UTC — convert to local time for display.
Packages & Limits
Every account has a package that defines how much you can use — how many schemas, how many IDs per month, and so on. From free trial to the largest plan, everything is clearly tiered.
🔧 Under the hood: Limits are enforced server-side. Monthly usage is counted in Redis and reset at the start of each month; schema and mapping limits are checked against the current count. When a limit is reached, a clear
403response is returned with a hint — generation fails in a controlled way instead of silently losing data.
Every tenant belongs to a package with defined quotas.
| Package | Schemas | IDs/month | Mapping tables | Note |
|---|---|---|---|---|
| Discovery | 2 | 250 | 1 | 14-day trial period |
| Orbit | 5 | 2,000 | 1 | — |
| Galaxy | 10 | 5,000 | 2 | — |
| Universe | 20 | 10,000 | 5 | — |
Monthly ID usage is tracked automatically and resets at the start of each month.
Check Your Usage
curl https://ouruka.com/v2/me -H "X-API-KEY: $KEY"
{
"package": "Orbit",
"limits": { "max_schemas": 5, "monthly_ids": 2000, "max_mapping_tables": 1, "max_mapping_entries": 50 },
"usage": { "schemas": 2, "ids_this_month": 341, "mapping_tables": 1 }
}
What Happens at the Limit?
- Schema limit reached →
POST /v2/schemasresponds with403and a hint. - Monthly ID limit reached →
POST /v2/generate/...responds with403. - Error responses are JSON, e.g.:
{ "error": "Monthly ID limit reached (2000/2000).",
"upgrade_hint": "Upgrade to Galaxy for a higher quota." }
Current package details and pricing are available at https://ouruka.com.
Security & Compliance
This chapter describes the technically implemented security measures of Ouruka. It is a factual description of the controls in the source code and operations — not a guarantee of formal certification (e.g. ISO 27001, SOC 2). Where compliance frameworks are mentioned, it refers to how individual requirements are addressed by concrete measures.
Transport Security
All traffic runs over HTTPS/TLS. Unencrypted connections are not served.
Why no "end-to-end encryption" — and why that's the right call here
End-to-end encryption (E2E) is familiar from messengers like Signal: a message is encrypted on the sender's device and only decrypted on the recipient's device — the provider in between sees only ciphertext. This works because a messenger is just a courier: it carries the sealed envelope without ever needing to know the contents.
Ouruka is not a courier, it's a baker: its job is to compute the ID — read the pattern, increment the counter, assemble date and variables. To do that, the service must understand the ingredients and produce the result in plain text. A baker who can't see the ingredients can't bake — and an ID service that can't read the inputs can't build a structured ID. E2E is simply the wrong tool for this kind of service — not out of convenience, but by principle.
What actually matters is what genuinely protects — and that's what is implemented:
| Protection | What it does | At Ouruka |
|---|---|---|
| TLS (transport) | Protects data in transit between you and the service | ✅ always |
| Credentials hashed | Even a database breach yields no usable keys | ✅ (SHA-256) |
| Data minimisation | Unnecessary data is never stored in the first place | ✅ no password system, UID masking |
| Deletion on request | Data can be fully removed | ✅ cascading tenant deletion |
| Encryption at rest | Stolen storage media stays unreadable | Infrastructure option |
In short: for a computing service like Ouruka, the goal is not "the server must never see anything" but rather "as little as necessary — securely transmitted, securely stored, deletable at any time". That's exactly what we deliver.
Authentication & Credentials
- API keys are never stored in plain text, only as SHA-256 hashes. The raw key is output exactly once (when created or rotated) and never again. Even read access to the database yields no usable keys.
- The administrative master key is compared timing-safely (
crypto/subtle.ConstantTimeCompare) — preventing stepwise guessing via timing measurements. - The master key exists only as an environment variable on the server — never in source code or the repository — and is never written to logs.
- No default/hardcoded credentials: Without correctly set configuration (DB, Redis, master key), the service does not start. There are no "forgotten" back doors.
Permissions & Abuse Protection
- Two separate levels: Tenant key (
X-API-KEY) for your own data only, master key (X-MASTER-KEY) for administrative operations. Customers cannot self-register. - Privilege boundary: The internal unlimited package cannot be assigned via the API (responds
403) — only manually in the backend. - Rate limiting on public endpoints (60 requests/minute/IP) against abuse and overload.
- Key rotation as a response to compromised keys: new key immediately valid, old one immediately invalid — no downtime.
Data Integrity
- Counters are incremented via an atomic Redis operation — even under massive concurrency, each value is assigned exactly once (load test: 500,000 IDs, 0 duplicates).
- Schemas are immutable — a running number series cannot be redefined retroactively.
- Inputs are validated (e.g. allowed UID/hash lengths, patterns, mapping keys).
Privacy by Design
- Data minimisation: No username/password system; the API key serves as both identity and credential. No unnecessary personal data is collected.
- Masking: Random UIDs are stored in history only in masked form (last 5 characters visible).
- Right to deletion: When a tenant is deleted, all associated data (schemas, history, counters, mappings) is cascadingly removed.
- Timestamps are consistently stored in UTC.
Auditability
- Every request receives a unique
X-Request-ID, which appears in structured JSON logs — the foundation for error analysis, monitoring, and audits.
Mapping to Common Requirements
The measures above address, among others, the following frequently audited principles (without claiming completeness or certification):
| Principle | Implementation at Ouruka |
|---|---|
| Confidentiality of credentials | Keys hashed only (SHA-256), master key timing-safe, never in code |
| Encryption in transit | HTTPS/TLS |
| Data minimisation (GDPR Art. 5) | No password system, no unnecessary personal data |
| Right to erasure (GDPR Art. 17) | Cascading tenant deletion |
| Integrity & availability | Atomic counters, rate limiting, fail-open on cache failure |
| Logging / auditability | Request IDs + structured logs |
FAQ
Are the IDs truly unique?
Yes. Ouruka guarantees uniqueness within a schema (schema IDs) and within a tenant (UIDs). Counters are incremented atomically — even with parallel requests, each counter value is assigned only once. Proven by load tests: 500,000 IDs under full load, 0 duplicates.
What happens if two systems request an ID at the same time?
Ouruka uses an atomic counter operation (Redis Lua script) that eliminates race conditions. Even two requests arriving within the same millisecond receive different, unique IDs.
Can I reset the counter?
There is no separate reset endpoint. For periodic resets, use an interval counter ({COUNTER:N:yearly} etc.) — it resets automatically. A one-time reset is possible by deleting the schema and recreating it (the counter then restarts at start_value). Note: with an unchanged pattern, previously issued values may be reissued — in that case also adjust the pattern (e.g. add a year segment or a fixed prefix).
What is the difference between schema IDs and UIDs?
Schema IDs follow a custom-defined pattern and can carry readable information (date, counter, variables) — ideal for document or product numbers. UIDs are purely random and semantically empty — ideal for technical keys and tokens.
How secure is the API key?
Transmitted only over HTTPS; keys are never stored in plain text (only as SHA-256 hash); comparison runs timing-safely. If you suspect a key has been compromised, rotate it immediately. Always store keys outside of source code.
Can I have multiple schemas?
Yes, depending on your package. Each schema has its own independent counter, history, and pattern — typically one schema per document type (orders, invoices, customers …).
What happens if a variable value is empty?
The placeholder is replaced with an empty string; with separator syntax ({VAR1:-}), the separator is also omitted — no double separators. Date placeholders are never empty (always the system date).
How is the performance?
ID generation typically responds in the double-digit to low triple-digit millisecond range; in load tests, 1,000+ IDs/second were measured at 0 errors and 0 duplicates. For specific SLA requirements, please contact support.
Is Ouruka easy to integrate?
Yes. Standard REST API with JSON — only HTTP requests needed, no SDK dependencies, no proprietary protocols. Every language that can do HTTP is compatible (Python, Java, PHP, JavaScript …), as are low-code platforms like Make or Zapier.
API Reference (Swagger)
The complete, machine-readable API reference is available interactively at:
https://ouruka.com/swagger/
There you will find:
- All endpoints with methods, paths, and descriptions.
- Request/response schemas including data types and required fields.
- Interactive testing directly in the browser — click "Authorize", enter your
X-API-KEY, and try out endpoints. - Error codes and their meaning.
Endpoint Overview
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
System status (public) |
GET |
/v2/changelog |
Version history (public) |
GET |
/v2/me |
Tenant info, limits, usage |
POST |
/v2/me/rotate-key |
Rotate your own API key |
POST / GET / DELETE |
/v2/schemas … |
Manage schemas |
POST |
/v2/generate/{schema} |
Generate an ID |
GET |
/v2/history |
History (pagination) |
POST |
/v2/uid/numeric · /v2/uid/alpha |
Random IDs |
POST |
/v2/keyify |
Text → key |
POST / GET / DELETE |
/v2/mappings/... |
Mapping tables |
Open-Source Components & Audit
This overview serves as a Software Bill of Materials (SBOM) and as a starting point for an open-source audit. It lists all third-party components that Ouruka is built from or uses at runtime, along with version and licence.
Note on authority: Licence information reflects general knowledge about the respective projects. For a formal audit, an automated licence/SBOM scan should also be run (see Reproducing the SBOM), and the respective
LICENSEfile should be checked in case of doubt.
The Application Itself
| Property | Value |
|---|---|
| Name / module | Ouruka ID Service (id-service) |
| Language / toolchain | Go 1.25 (toolchain 1.26), standard library under BSD-3-Clause |
| Application licence | Proprietary (no open-source release) — does not itself trigger OSS obligations |
| Architecture | Monolith (net/http, no framework) + PostgreSQL + Redis, Docker |
Go Modules Compiled into the Service Binary
Determined from the built binary (go version -m) — this is the authoritative runtime bill of materials.
| Component | Version | Purpose | Licence |
|---|---|---|---|
github.com/lib/pq |
v1.11.2 | PostgreSQL driver | MIT |
github.com/redis/go-redis/v9 |
v9.18.0 | Redis client | BSD-2-Clause |
github.com/cespare/xxhash/v2 |
v2.3.0 | Hash (Redis client dep) | MIT |
github.com/dgryski/go-rendezvous |
(pseudo) | Hashing (Redis client dep) | MIT |
go.uber.org/atomic |
v1.11.0 | Atomics | MIT |
github.com/swaggo/http-swagger |
v1.3.4 | Swagger UI handler | MIT |
github.com/swaggo/files |
v1.0.1 | Swagger UI assets | MIT |
github.com/swaggo/swag |
v1.16.6 | Swagger spec | MIT |
github.com/go-openapi/spec |
v0.20.6 | OpenAPI spec | Apache-2.0 |
github.com/go-openapi/jsonpointer |
v0.19.5 | OpenAPI | Apache-2.0 |
github.com/go-openapi/jsonreference |
v0.20.0 | OpenAPI | Apache-2.0 |
github.com/go-openapi/swag |
v0.19.15 | OpenAPI | Apache-2.0 |
github.com/KyleBanks/depth |
v1.2.1 | Swag dep | MIT |
github.com/josharian/intern |
v1.0.0 | JSON dep | MIT |
github.com/mailru/easyjson |
v0.7.6 | JSON | MIT |
golang.org/x/net |
v0.34.0 | Networking | BSD-3-Clause |
golang.org/x/mod |
v0.17.0 | Module parsing | BSD-3-Clause |
golang.org/x/tools |
(pseudo) | Tooling | BSD-3-Clause |
gopkg.in/yaml.v2 |
v2.4.0 | YAML | Apache-2.0 |
Functionally relevant at runtime are primarily lib/pq and redis/go-redis; the rest serves the embedded
/swagger/endpoint. Development/test only (not in the binary):stretchr/testify,bsm/ginkgo,bsm/gomega,google/go-cmp.
Container Images (Operations)
| Image | Specific version | Role | Licence note |
|---|---|---|---|
golang:1.25-alpine |
— | Build (multi-stage) | Go: BSD-3; build only, not in the delivery image |
alpine:3.21 |
— | Runtime base | musl (MIT), BusyBox (GPLv2), apk-tools (GPLv2) — base OS, used unmodified |
postgres:16-alpine |
PostgreSQL 16.13 | Database | PostgreSQL Licence (permissive, BSD/MIT-like) |
redis:7-alpine |
Redis 7.4.8 | Cache / counters | ⚠️ RSALv2 / SSPLv1 (see Finding below) |
Third-Party Systems (Operations)
- PostgreSQL 16 — persistent storage. Licence: PostgreSQL Licence (permissive).
- Redis 7.4 — atomic counters and rate-limit buckets. Licence: RSALv2/SSPLv1 (see Finding).
- Reverse proxy / TLS termination (infrastructure, outside this repo).
Findings for the Auditor
- ⚠️ Redis 7.4.8 is no longer OSI open source. From Redis 7.4 onwards, the dual licence RSALv2 / SSPLv1 applies (source-available). For pure in-house use as a dependency, this is generally non-critical, but an OSS audit will flag it as a non-OSS component. Decision: Ouruka deliberately continues to use Redis — permitted and free for in-house use. Should a strict "OSI open source only" requirement ever arise, there are two straightforward alternatives (
redis:7.2-alpine, still BSD-3-Clause, or the compatible fork Valkey); neither is currently needed. - The Alpine base includes GPLv2 components (BusyBox, apk-tools). They are used as an unmodified OS base and not linked into the application — standard practice, but worth listing.
- All Go modules compiled into the binary are permissive (MIT / BSD / Apache-2.0). No copyleft (GPL/LGPL) in the application binary.
- Version pinning:
redis:7,postgres:16follow the major tag (float within major). For an audit, pin to exact versions or image digests.
In Plain Language: Why We Disclose the Redis Point
We could hide this — we deliberately choose not to. Transparency is a quality mark for us, not a risk.
Redis is a ready-made component from another vendor, which Ouruka uses as a fast "counting engine". Every piece of software comes with a licence — the terms of use. Redis had long been the most permissive kind (anyone can do anything). In 2024, the creators changed the terms from version 7.4: now "source-available" — the code remains viewable and free and permitted for in-house use, but officially no longer qualifies as "true open source". The restriction targets large cloud providers who resell Redis as their own paid service — not users like us.
The analogy: A specialised oven in a restaurant whose manufacturer changes the terms from "use it freely" to "use it yes — but no oven-rental business". For cooking, nothing changes; an auditor notes the point regardless.
For us this means: permitted, free, unproblematic in operation. A formal OSS audit will flag the component as "non-standard open source" — but we deliberately continue to use Redis and openly disclose the point here. Should a strict "OSI open source only" requirement ever arise, Valkey (a technically compatible fork) or Redis 7.2 are two straightforward paths — not currently needed.
Reproducing the SBOM
# Runtime bill of materials of the binary (authoritative):
go build -o /tmp/ouruka . && go version -m /tmp/ouruka
# Full module graph:
go list -m all
# Specific service versions in operation:
docker exec ouruka-postgres postgres --version
docker exec ouruka-redis redis-server --version
Recommended for a formal audit (not installed in this repo): generate a machine-readable SBOM, e.g. with cyclonedx-gomod mod -json (CycloneDX) or syft / trivy for Go and container images, as well as a licence scan with go-licenses report ./....