eudr.dev Agent API

Stateless, semantic /v1/* surface for AI agents.

GitHub OpenAPI

Operations 9

0 of 9 operations are conformant: apis.io called them with no credentials and the responses matched this contract; 2 more answered but could not be checked against a schema. Checked 2026-11-12. how this works · status file

POST /v1/spatial/verify Plot polygon → deforestation verdict + evidence CID #
GET /v1/evidence/{evidence_cid} Page the fact set behind a check #
GET /v1/trace/{task_id} The complete derivation chain for a compiled statement #
POST /mcp Model Context Protocol endpoint (JSON-RPC, not REST) #
POST /v1/dds/generate Operator + supplier + product + geolocation → signed Annex II Due Diligence… #
GET /api/dds/{task_id}/traces.xml The TRACES NT submission XML for a negligible statement (409 for any other… #
POST /v1/receipt/verify Signed receipt JSON → boolean + re-derived CID + signer pubkey #
GET /v1/reference/countries Country risk tiers per Implementing Reg. (EU) 2025/1093 observed #
GET /v1/reference/commodities Annex I commodity → HS prefix mapping per Reg. (EU) 2023/1115 observed #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/eudr-dev:eudr-dev-agent-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

eudr-dev-agent-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: eudr.dev — EU Deforestation Regulation compliance Agent API
  version: 0.3.0
  description: eudr.dev is an independent AI agent that helps companies, SMEs, suppliers, buyers — and the AI agents that act on their behalf — navigate Reg. (EU) 2023/1115 via an A2A-native interface. Built on emem (https://github.com/Vortx-AI/emem), the open-source Earth-memory protocol that supplies every signed per-cell fact this agent cites. Generates signed Annex II Due Diligence Statements with content-addressed evidence and offline-verifiable receipts, not affiliated with, endorsed by, or sponsored by the European Commission or any EU institution. See /legal/disclaimer for the scope of service and /docs/audit/verify-offline for the offline-verification procedure.
  contact:
    name: Vortx AI Private Limited
    email: avijeet@vortx.ai
servers:
- url: https://eudr.dev
  description: This deployment
tags:
- name: Agent
  description: Stateless, semantic /v1/* surface for AI agents.
paths:
  /v1/spatial/verify:
    post:
      summary: Plot polygon → deforestation verdict + evidence CID
      description: Hansen GFC + ESA WorldCover + JRC GFC2020 via emem.dev. Returns the verdict against the 2020-12-31 cut-off in Art. 2(13) Reg. (EU) 2023/1115.
      operationId: spatialVerify
      tags:
      - Agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SpatialVerifyBody'
      responses:
        '200':
          description: Verdict + evidence
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SpatialVerifyResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          description: Rate limit (60 rpm sliding window per IP)
  /v1/evidence/{evidence_cid}:
    get:
      summary: Page the fact set behind a check
      description: Resolve an `evidence_cid` minted by /v1/spatial/verify. The address is BLAKE3 over the canonical CID list, so it is re-derivable from the CIDs a response returned. A 404 means the handle is not held by this process (expired, or a restart); re-running the check returns the same measurements under new addresses rather than recovering these bytes, because each fact is signed with its signing time. For a citation that must outlive a day, cite the statement receipt.
      operationId: resolveEvidence
      tags:
      - Agent
      parameters:
      - name: evidence_cid
        in: path
        required: true
        schema:
          type: string
      - name: offset
        in: query
        schema:
          type: integer
          minimum: 0
          default: 0
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 200
          default: 50
      responses:
        '200':
          description: One page of content-addressed facts.
        '404':
          description: No evidence set is held for this address.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
  /v1/trace/{task_id}:
    get:
      summary: The complete derivation chain for a compiled statement
      description: Everything here is already inside the signed statement; what this adds is order and attribution. Steps run from the polygon through grid addressing, signed measurement, algorithm evaluation against pinned registries, aggregation, the consignment verdict, citations, the independent guard verdict, the engine signature and the transparency-log anchor. Three keys sign parts of the chain and they are not interchangeable — `signing_parties` keeps them separate. A stage whose evidence is absent is emitted as absent WITH a reason rather than omitted, and `not_established` states what the chain does not show.
      operationId: executionTrace
      tags:
      - Agent
      parameters:
      - name: task_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The derivation chain (schema eudr.execution_trace.v1).
        '404':
          description: No compile is held under that task_id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '409':
          description: The compile has not produced a statement yet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '410':
          description: The statement was erased under GDPR Art. 17.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
  /mcp:
    post:
      summary: Model Context Protocol endpoint (JSON-RPC, not REST)
      description: Dual-era Streamable HTTP MCP. Answers the legacy `initialize` handshake and the stateless 2026-07-28 revision (`server/discover`, per-request `_meta` protocol version, `resultType`, `ttlMs`/`cacheScope`, `Mcp-Method`/`Mcp-Name` validated against the body). This is NOT a REST resource and is described here only so a reader of this document knows it exists; the tool catalog is served by `tools/list`, and /.well-known/mcp.json carries the connection details. GET returns 405.
      operationId: mcp
      tags:
      - Agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: A single JSON-RPC 2.0 request or notification. Batching is not supported.
      responses:
        '200':
          description: JSON-RPC response.
        '202':
          description: Notification accepted, no body.
        '400':
          description: Header/body mismatch (-32020) or unsupported protocol version (-32022).
        '404':
          description: Unknown method (-32601).
  /v1/dds/generate:
    post:
      summary: Operator + supplier + product + geolocation → signed Annex II Due Diligence…
      description: 'Runs the 7-stage compile pipeline: geo → forest → legality → tenure → risk → dds → settle. Returns a SignedReceipt envelope (ed25519, CIDv1).'
      operationId: ddsGenerate
      tags:
      - Agent
      security:
      - bearerAuth: []
      - oauth2:
        - eudr:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DDSGenerateBody'
      responses:
        '200':
          description: Task started; poll /a2a/tasks/{id} until state=completed.
          content:
            application/json:
              schema:
                type: object
        '402':
          description: The organisation's credit wallet does not cover this compile. Nothing was charged and nothing was compiled.
        '429':
          description: Rate limit
  /api/dds/{task_id}/traces.xml:
    get:
      summary: The TRACES NT submission XML for a negligible statement (409 for any other…
      operationId: tracesXml
      tags:
      - Agent
      parameters:
      - name: task_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: application/xml
        '409':
          description: Not a filable (negligible) statement
  /v1/receipt/verify:
    post:
      summary: Signed receipt JSON → boolean + re-derived CID + signer pubkey
      operationId: receiptVerify
      tags:
      - Agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignedReceipt'
      responses:
        '200':
          description: Verification result.
  /v1/reference/countries:
    get:
      summary: Country risk tiers per Implementing Reg. (EU) 2025/1093
      operationId: referenceCountries
      tags:
      - Agent
      responses:
        '200':
          description: 'Country table. Cacheable (Cache-Control: public, max-age=86400, immutable) with strong BLAKE3 ETag.'
  /v1/reference/commodities:
    get:
      summary: Annex I commodity → HS prefix mapping per Reg. (EU) 2023/1115
      operationId: referenceCommodities
      tags:
      - Agent
      responses:
        '200':
          description: Commodity table. Cacheable.
components:
  schemas:
    DDSGenerateBody:
      type: object
      description: 'Either this shape, or a sample scenario as published at /samples/scenarios/*.json (with `production` for `geolocation` and `product.quantity_kg` for `net_mass_kg`), bare or wrapped as {"scenario": ...}. A missing field is named in the 400.'
      required:
      - operator
      - supplier
      - product
      - geolocation
      properties:
        operator:
          type: object
          required:
          - name
          - country_of_establishment
          properties:
            name:
              type: string
            country_of_establishment:
              type: string
              description: ISO-3166-1 alpha-2
            address:
              type: string
            eori:
              type: string
            role:
              type: string
              enum:
              - operator
              - trader
        supplier:
          type: object
          required:
          - name
          - id
          properties:
            name:
              type: string
            id:
              type: string
            country:
              type: string
            address:
              type: string
            email:
              type: string
        product:
          type: object
          required:
          - hs_code
          - description
          - net_mass_kg
          properties:
            hs_code:
              type: string
              example: '1801.00'
            description:
              type: string
            net_mass_kg:
              type: number
            trade_name:
              type: string
            scientific_name:
              type: string
        geolocation:
          type: object
          required:
          - country
          - harvest_window
          - plot_geojson
          properties:
            country:
              type: string
              description: ISO-3166-1 alpha-2 country of production
            harvest_window:
              type: string
              example: 2025-10-12/2026-02-08
            plot_geojson:
              $ref: '#/components/schemas/GeoJSONGeometry'
            plot_id:
              type: string
              description: 'The plot''s own id (a field, cadastral or cooperative code); it is filed under it. Never a person''s name: it is carried into a signed, public, append-only fact at the evidence provider.'
            plot_area_ha:
              type: number
              description: 'For a Point plot: its area in hectares. Art. 2(28) allows a Point only for a plot of 4 ha or less; over 4 ha is refused.'
            additional_plots:
              type: array
              description: Every further plot of the consignment.
              items:
                type: object
                required:
                - plot_id
                - plot_geojson
                properties:
                  plot_id:
                    type: string
                  plot_geojson:
                    $ref: '#/components/schemas/GeoJSONGeometry'
                  country:
                    type: string
                    description: When the plot is in another country than the consignment's.
                  harvest_window:
                    type: string
                  quantity_kg:
                    type: number
                    description: This plot's share; undeclared shares split the rest evenly.
                  area_ha:
                    type: number
                    description: 'For a Point plot: its area in hectares (4 ha or less).'
            country_subregion:
              type: string
        producer:
          type: object
          properties:
            name:
              type: string
            id:
              type: string
    SpatialVerifyResponse:
      type: object
      description: The verdict, what was inspected to reach it, and what was NOT checked. Every response names the unresolved checks so a clean deforestation result is never read as a clean due-diligence file.
      properties:
        deforestation_free:
          type:
          - boolean
          - 'null'
        post_cutoff_loss_cells:
          type: integer
          description: 'Signed population tally of cells with post-cut-off canopy loss, not the shipped sample. This is loss OBSERVED, not deforestation found: see `in_scope_loss_cells`.'
        in_scope_loss_cells:
          type: integer
          description: Cells with post-cut-off loss on the plots that trip the Art. 2(13) gate — the loss the Regulation counts as deforestation. Below `post_cutoff_loss_cells` whenever the responder cleared a plot as non-forest at the cut-off, or under the 0.5 ha the Art. 2(4) forest definition requires. A row the responder cleared does not bar the consignment.
        sub_mmu_loss_ha:
          type: number
          x-conditional: true
          description: Hectares of sub-floor loss across the consignment, present only when they reach the floor together and that is why the verdict is indeterminate. One patch below 0.5 ha is not forest; a clearing split across twenty plots is.
        forest_baseline:
          type: string
          description: The forest baseline the responder actually read, as it named it (e.g. `jrc_gfc2020_v4`).
        precision_warnings:
          type: array
          items:
            type: object
          x-conditional: true
          description: The responder's warnings, verbatim, that a plot's coordinates carry fewer than the six decimal digits Art. 2(28) requires of a geolocation. Omitted when there are none.
        total_cells:
          type: integer
        verdict:
          type: string
          enum:
          - negligible
          - non_negligible
          - not_in_scope
          - indeterminate
          description: 'Consignment verdict. `not_in_scope` here means the commodity is outside Annex I. A per-plot row''s `not_in_scope` means something else: the land was not forest at the 2020 cut-off, so the plot is deforestation-free by definition, and the consignment verdict can still be `negligible`.'
        risk_score:
          type: number
          nullable: true
        country_tier:
          type: string
          enum:
          - low
          - standard
          - high
        cut_off_date:
          type: string
          example: '2020-12-31'
        per_plot:
          type: array
          description: One row per submitted plot, in submission order. A single-plot request still gets one row.
          items:
            type: object
            properties:
              plot_id:
                type: string
              verdict:
                type: string
              cells_evaluated:
                type: integer
              cells_with_post_cutoff_loss:
                type: integer
              area_ha_approx:
                type: number
                nullable: true
              loss_assessment:
                type: string
                enum:
                - in_scope_deforestation
                - not_forest_at_cut_off
                - below_minimum_mapping_unit
                - no_loss
                - undecided
                description: What the loss on this plot amounts to in law. A cell count alone does not say, and reading it as though it did barred consignments the same response cleared.
              loss_area_ha:
                type: number
                description: Hectares of post-cut-off loss, as the responder measured it. Omitted when it did not report one.
              precision_warning:
                type: string
                description: The responder's warning that this plot's coordinates are below the six decimal digits Art. 2(28) requires. Omitted when clean.
        coverage:
          type: object
          description: What was actually inspected. A verdict over 2 % of a polygon and a verdict over all of it are different claims.
          properties:
            cells_evaluated:
              type: integer
            cell_size_m:
              type: number
              example: 9.55
            sampled_area_ha:
              type: number
              nullable: true
            plot_area_ha:
              type: number
              nullable: true
            sampled_polygon_fraction:
              type: number
              nullable: true
              description: 1.0 is a full sweep.
            sampling_note:
              type: string
              nullable: true
              description: The upstream responder's own disclosure, verbatim.
            per_cell_rows_returned:
              type: integer
              description: How many per-cell rows came back with the answer. `cells_evaluated` is the responder's signed population tally and is what every count here is on; these rows are the sample it shipped, and they are fewer.
        loss_years_observed:
          type: array
          items:
            type: integer
          description: Distinct Hansen loss years in the shipped sample, ascending. Dates what was found; never counts it.
        unresolved_checks:
          type: array
          items:
            type: string
          description: Checks this engine does not perform. Art. 3(b) legality is documentary and unobserved here.
        evidence:
          type: object
          description: 'Note the nesting: `evidence_cid` lives HERE, not at the top level. An earlier revision of this document put it at the top level, which is a field the response has never carried.'
          properties:
            evidence_cid:
              type: string
              nullable: true
              description: 'Content address of THIS response''s fact list. BLAKE3 over its canonical JSON, so a caller can re-derive it from the returned CIDs. It is not a fingerprint of the question: emem signs each fact with the moment it was signed, so re-running the same check returns the same measurements under new addresses. Resolve at /v1/evidence/{evidence_cid}.'
            fact_count:
              type: integer
              description: How many facts the check resolved. Always present.
            fact_cids:
              type: array
              items:
                type: string
              description: 'Omitted entirely when `evidence_detail: "anchor"` was sent.'
            responder_pubkey_b32:
              type: string
              nullable: true
            algorithm:
              type: string
              nullable: true
        engine:
          type: string
    ApiError:
      type: object
      description: 'Every rejection on the /v1/* surface. A malformed body returns this with HTTP 400 and `error: "invalid_request"` — not a plain-text 422.'
      required:
      - error
      - message
      properties:
        error:
          type: string
          description: Stable code to branch on, so a caller never string-matches prose that is free to be reworded.
          enum:
          - invalid_request
          - invalid_json
          - invalid_content_type
          - upstream_unavailable
          - rate_limit_exceeded
          - evidence_not_held
          - task_not_found
          - not_compiled
          - erased
          - error
        message:
          type: string
          description: A sentence a human can read.
        field:
          type: string
          nullable: true
          description: The field at fault, where one is known.
    SpatialVerifyBody:
      type: object
      required:
      - commodity
      - country
      description: Supply either `polygon` (one plot) or `plots` (up to eight). Supplying neither is a 400 naming the omission.
      properties:
        polygon:
          $ref: '#/components/schemas/GeoJSONGeometry'
        plots:
          type: array
          minItems: 1
          maxItems: 25
          description: Several plots in one request, each keeping its own verdict row. Art. 4(1) taints the whole consignment if any single plot fails, so they belong in one call.
          items:
            type: object
            required:
            - geojson
            properties:
              plot_id:
                type: string
                maxLength: 64
                description: Echoed on the matching verdict row. [A-Za-z0-9-_.:] Keep it opaque — a farm or cadastral code, never a person's name. This one field is carried into a signed, public, append-only fact at the evidence provider, which cannot erase it without breaking the receipts that cite it.
              geojson:
                $ref: '#/components/schemas/GeoJSONGeometry'
        commodity:
          type: string
          enum:
          - cattle
          - cocoa
          - coffee
          - oil_palm
          - rubber
          - soya
          - wood
        country:
          type: string
          description: ISO-3166-1 alpha-2.
          minLength: 2
          maxLength: 2
        quantity_kg:
          type: number
          nullable: true
        hs_code:
          type: string
          nullable: true
        evidence_detail:
          type: string
          enum:
          - cids
          - anchor
          default: cids
          description: '`cids` embeds every per-cell fact CID; `anchor` returns only the address and count, resolvable at /v1/evidence/{evidence_cid}. The verdict is identical either way. On a real plot the array is the large majority of the body.'
    SignedReceipt:
      type: object
      description: ed25519-signed Annex II envelope. Verifiable offline — see /docs/audit/verify-offline.
      required:
      - payload
      - cid
      - signature_b64
      - pubkey_b32
      properties:
        payload:
          type: object
          description: Canonical Annex II JSON.
        cid:
          type: string
          description: CIDv1 of the canonical payload (BLAKE3 + multibase 'b' base32).
        signature_b64:
          type: string
          description: ed25519 signature over the CID bytes, base64-encoded.
        pubkey_b32:
          type: string
        did:
          type: string
        issued_at:
          type: string
          format: date-time
    GeoJSONGeometry:
      type: object
      description: GeoJSON Geometry — Polygon, MultiPolygon, or Point. WGS84 (EPSG:4326). Coordinates ordered [lng, lat].
      required:
      - type
      - coordinates
      properties:
        type:
          type: string
          enum:
          - Polygon
          - MultiPolygon
          - Point
        coordinates: {}
      examples:
      - type: Polygon
        coordinates:
        - - - -6.11
            - 5.2
          - - -6.11
            - 5.205
          - - -6.105
            - 5.205
          - - -6.105
            - 5.2
          - - -6.11
            - 5.2
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'An API key: sign in at /auth, open /app/#/api-keys, create one (shown once).'
    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://eudr.dev/oauth/authorize
          tokenUrl: https://eudr.dev/oauth/token
          scopes:
            eudr:read: read the organisation's records
            eudr:write: compile and sign statements
externalDocs:
  url: https://eudr.dev/docs
  description: Theory, methodology, architecture, and offline-verification guide.